Pour une expériencenet et une sécurité optimale, mettez à jour votre navigateur. Mettre à jour maintenant

Si comme moi tu t’es réveillé avec ton plan Terraform qui ne passe plus après une mise à jour OVH de l’OpenStack en version Stein. Ce qui va suivre devrait t’intéresser.
J’avais cette erreur-là :
Exceeded maximum number of retries. Exceeded max scheduling attempts 6 for
instance XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. Last exception: Network requires
port_security_enabled and subnet associated in order to apply security groups.Concrètement, tu as un plan Terraform comme ceci :
# main.tf
resource "openstack_compute_instance_v2" "vm1" {
name = "vm1"
image_name = "ubuntu"
flavor_name = "d1-2"
security_groups = ["default", "ssh"]
network {
name = "Ext-Net"
}
network {
name = "vrack"
}
}Au moment du terraform apply, tu as l’erreur ci-dessus. Et même pire que ça,
Terraform tourne en boucle.
C’est le passage en Stein qui est en cause.
Avant, tu avais ta VM. Tu lui spécifiais :
Openstack créait automatiquement les ports (interfaces réseau virtuelles) et Terraform appliquait les security groups à la machine, donc à l’ensemble des ports (publics comme vrack).
Maintenant, il ne faut appliquer les security groups que sur les ports externes et non au niveau de l’ensemble des ports.
La bonne nouvelle, c’est qu’à terme on va pouvoir enfin mettre des security groups aux interfaces assignées aux vracks sur toutes les régions, comme c’est déjà possible sur GRA9 !!!
Bon, comment tu fais marcher tout ça ?!
# main.tf
# Pour avoir les UUID des réseaux plus tard
data "openstack_networking_network_v2" "net_public" {
name = "Ext-Net"
}
data "openstack_networking_network_v2" "net_vrack" {
name = vrack
}
# Pour avoir les UUID des security groups plus tard
data "openstack_networking_secgroup_v2" "default" {
name = "default"
}
data "openstack_networking_secgroup_v2" "ssh" {
name = "ssh"
}
# Maintenant les ports
# Le public avec les security groups
resource "openstack_networking_port_v2" "public" {
name = "vm1-public"
network_id = data.openstack_networking_network_v2.net_public.id
security_group_ids = [
data.openstack_networking_secgroup_v2.default.id,
data.openstack_networking_secgroup_v2.ssh.id
]
admin_state_up = "true"
}
# Le privé sans security group
resource "openstack_networking_port_v2" "priv" {
name = "vm1-priv"
network_id = data.openstack_networking_network_v2.net_vrack.id
admin_state_up = "true"
}
# Et enfin la VM
resource "openstack_compute_instance_v2" "vm1" {
name = "vm1"
image_name = "ubuntu"
flavor_name = "d1-2"
network {
port = openstack_networking_port_v2.public.id
}
network {
port = openstack_networking_port_v2.priv.id
}
}Et voilà ! :)

If like me you woke up with your Terraform plan no longer passing after an OVH update of the OpenStack to Stein. What follows should be of interest to you.
I had this error:
Exceeded maximum number of retries. Exceeded max scheduling attempts 6 for
instance XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. Last exception: Network requires
port_security_enabled and subnet associated in order to apply security groups.Concretely, you have a Terraform plan like this:
# main.tf
resource "openstack_compute_instance_v2" "vm1" {
name = "vm1"
image_name = "ubuntu"
flavor_name = "d1-2"
security_groups = ["default", "ssh"]
network {
name = "Ext-Net"
}
network {
name = "vrack"
}
}At the time of the terraform apply, you have the above error. And even worse than that,
Terraform is running in a loop.
It is the change to Stein which is in cause.
Before, you had your VM. You would specify to it:
Openstack automatically created the ports (virtual network interfaces) and Terraform applied the security groups to the machine, i.e. to all the ports (public as well as ports (both public and vrack).
Now, you have to apply security groups only on external ports and not on all ports.
The good news is that in the future we will finally be able to put security groups to the interfaces assigned to the vracks on all the regions, as it is already possible on GRA9 !!!
Well, how do you make it all work?!
# main.tf
# To have the UUID of the networks later
data "openstack_networking_network_v2" "net_public" {
name = "Ext-Net"
}
data "openstack_networking_network_v2" "net_vrack" {
name = vrack
}
# To have the UUID of the security groups later
data "openstack_networking_secgroup_v2" "default" {
name = "default"
}
data "openstack_networking_secgroup_v2" "ssh" {
name = "ssh"
}
# Now the ports
# Public with security groups
resource "openstack_networking_port_v2" "public" {
name = "vm1-public"
network_id = data.openstack_networking_network_v2.net_public.id
security_group_ids = [
data.openstack_networking_secgroup_v2.default.id,
data.openstack_networking_secgroup_v2.ssh.id
]
admin_state_up = "true"
}
# Private without security group
resource "openstack_networking_port_v2" "priv" {
name = "vm1-priv"
network_id = data.openstack_networking_network_v2.net_vrack.id
admin_state_up = "true"
}
# And finally the VM
resource "openstack_compute_instance_v2" "vm1" {
name = "vm1"
image_name = "ubuntu"
flavor_name = "d1-2"
network {
port = openstack_networking_port_v2.public.id
}
network {
port = openstack_networking_port_v2.priv.id
}
}And voila! :)

Bon, je ne vais pas revenir sur l’incident d’OVH :
Statut sur l’incident sur notre site de Strasbourg : https://t.co/zkwslZX34e
— OVHcloud Support FR (@ovh_support_fr) March 10, 2021
Mais ça fait un moment qu’un petit article sur l’analyse de risque me trotte dans la tête. Et visiblement, c’est le bon timing pour éviter d’être à nouveau dans la panade.
Disclamer: Les cordonniers sont les plus mal chaussés. Et personne n’est à l’abri d’une erreur Et je ne suis pas un “spécialiste” en sécurité, dans le sens ou ce n’est pas mon activité principale (audit, flux, etc.). Mais j’ai fait une spécialisation dans mes études ce qui m’a donné des bases. Et j’ai toujours appliqué ces préceptes dans ma vie pro/perso.
Bon, alors déjà c’est quoi un risque ? Sur Wikipedia, on peut lire deux pages : Risque et Sécurité des systèmes d’information
Ce que l’on peut retenir, c’est la définition : “Le risque est la possibilité de survenue d’un événement indésirable, la probabilité d’occurrence d’un péril probable ou d’un aléa”
Et une formule mathématique que je vais simplifier : r = p⋅C
En bon français, le risque est égal à la probabilité multipliée par la conséquence (financière par exemple).
Ensuite, une fois que l’on a évalué ce risque, on va choisir de le prendre en compte ou pas. Et donc le traiter si besoin.
Bon, c’est bien gentil tout ça, mais j’en fais quoi ? On va prendre quelques cas simples que j’ai rencontrés dans ma vie de sysadmin (le vieux nom de devops/sre/…).
Légende :
| Risques | probabilité | conséquence |
|---|---|---|
Je fais un rm -rf dans mes fichiers |
Haute | +/- |
Je fais un DELETE from ITEMS dans ma base de données |
Haute | +/- |
| Je me fais pirater (remplacement des fichiers) | Moyenne | ☠️ |
| Un composant de ma machine tombe en panne | Moyenne | ☠️ |
| Le disque dur tombe en panne | Moyenne | ☠️ |
| Ma machine ne redémarre pas | Moyenne | ☠️ |
| La machine brûle / l’hébergeur devient injoignable | Faible | ☠️ |
| Le continent où est hébergé le serveur disparaît | Très faible | ☠️ |
Bon, on peut aussi chercher à estimer les coûts, mais globalement on ne veut ni perdre de donnée ni que l’application soit indisponible.
rm -rf dans mes fichiersLà, c’est le problème à la fois le plus simple et le plus compliqué dont se protéger. Simple parce qu’un bête rsync peut suffire à réduire le risque. Et compliqué parce que l’on peut vouloir gérer un “historique”, mais aussi s’il y a trop de fichiers/volumétrie.
Bon, déjà pour gagner du temps pour plus tard, si vous en avez la possibilité, passez directement sur de l’object-storage. Vous allez anticiper pas mal de problèmes pour le futur :
Sinon, ce que je mets souvent en place, c’est :
Si des fichiers sont supprimés, je fais un rsync dans l’autre sens. À partir d’un snapshoot au besoin.
DELETE from ITEMS dans ma base de donnéesDans ce cas, je fais tous les jours des sauvegardes locales soit :
Ensuite, tout ça est copié via rsync sur le serveur de backup + un snapshoot.
Si j’ai besoin de restaurer, j’importe le fichier qui est local, ou je copie du serveur distant. Si c’est un peu plus “chigurgical”, le développeur a accès à une base sur laquelle il peut extraire les données dont il a besoin.
Vous savez, vous vous connectez sur votre site, et vous avez une revendication politique ; un truc du style hacktiviste.
Là, c’est très simple, on restaure les fichiers. Ensuite on cherche d’où vient la faille pour la corriger. Ou l’inverse si on a le temps.
Pensez à regarder si personne n’est connecté, sauvegardez/regardez les logs, etc. Dans l’idéal, une fois le problème corrigé, réinstallez complètement l’OS de zéro pour écarter tout risque de compromission résiduelle.
Là aussi c’est très simple. Ça arrive que la carte mère crame, le CPU, la RAM, etc. Le serveur ne répond plus ou perd les pédales. Une fois, j’ai eu un load average énorme au moindre curl. C’était la carte mère qui avait pris un jeton… Nous y avions passé la semaine à essayer de comprendre la source du problème…
Bref un ticket à OVH, on éteint le serveur, on change la pièce, et c’est reparti pour un tour :)
Bon, c’est clair que pendant ce temps là, l’application est indisponible. Mais tout le monde n’est pas prêt à payer pour du HA.
Et franchement en 15 ans d’hébergement, je n’ai pas eu tant que ça de problèmes…
Encore un problème simple à résoudre. On prend du RAID, on configure ça en RAID 1, 5, 6, JBOD,… tout ce que vous voulez. Mais JAMAIS du 0 sauf si vous voulez tout perdre :p
Un disque crame => ticket à OVH pour le changer.
Bien sûr, pensez à monitorer l’état du RAID. Ce serait con que le deuxième disque lâche avant le remplacement du premier. Oui, j’ai déjà vu ça…
Alors là, c’est le truc très con. Upgrade du noyau, reboot, et on attend……
Donc, prenez des serveurs avec un KVM (console Keyboard Video Mouse - pas d’intérêt d’avoir la souris pour nous, mais c’est le nom). Et connectez-vous AVANT de rebooter. Et pas juste une connexion au KVM, connectez-vous avec le compte login/pass. Un autre conseil au passage, désactivez la connexion SSH du root. Mais autorisez la connexion en console. Vous me remercierez en cas de galère. Et pensez au mapping du clavier que vous allez utiliser. Je suis passé en full QWERTY depuis 10 ans, ça m’évite pas mal de problèmes de ce côté-là. Et dans vos choix de pass, pensez à ceux qui ont de l’AZERTY/BEPO/…. Ils pourraient pleurer en tapant iWzyodO[OZ+CS}g|Y',]|Z2'b<(7/2Ez3kn :p Et vous aussi, donc privilégiez un truc du type lakeside-emporium-indulge-ARGOT, ce sera plus simple à taper dans l’urgence.
Bon, clairement un serveur qui brûle c’est rare. Un DC entier, c’est encore plus rare. Mais on a vu que c’était possible. Et quand j’expliquais ça il y a quelques années, les gens/collègues me regardaient en rigolant en me traitant de parano :D
Encore une fois, la probabilité que ça arrive est très faible. Mais le jour où ça arrive, vous n’avez plus rien. Donc pour se préparer à ça, il faut pouvoir :
Et comme votre serveur n’est plus là, aucune chance de savoir comment c’était configuré avant…
Donc :
Bon, l’Europe est engloutie sous les mers telle l’Atlantide, on se fait atomiser par un météorite comme nos ancêtres les dinosaures. Là, je dois vous avouer que je n’ai pas adressé ce problème. Mais comme je serais mort….
Toutes les semaines :
Tous les jours :
J’en parle, mais quoi et où ?
Pour réduire les risques :
Ça dépend de votre niveau de parano. Un backup sur votre NAS c’est bien aussi. Mais dans ce cas là, pensez à le chiffrer.
C’est sûr qu’avec la fibre optique vous avez de plus en plus la possibilité de vous héberger. Sauf que :
Et chez vous ? Personnellement, je n’ai plus rien de critique chez moi. Au pire, je me fais voler/détruire ma workstation et sa sauvegarde.
Et quand je parle d’intrusion, il y a le vol qui inclut la perte des données. Mais que se passerait-il si les données fuitaient par la suite ? GDPR et/ou concurrents ?
Je pense que l’article est déjà suffisamment long. Mais je pense que vous avez l’idée. Avec tout ça, nous avons un Plan de Reprise d’Activité.
Depuis que je mets tout ça en place, je dors beaucoup mieux. Je ne dis pas que je serais parfaitement serein si GRA/RBX brûlait, j’ai beaucoup plus de serveurs là-bas.
Mais globalement, je pense avoir adressé pas mal de problèmes.
Et à chaque nouvelle technologie, il faut se renouveler et recommencer cette analyse de risque.
Comment je gère mes vm “cloud”, comment je sauvegarde mon object storage ?
Nous n’avons pas non plus discuté du coût de ces contre-mesures, dans ce cas là, ce n’est pas vraiment négociable de faire sans. Par contre, nous n’avons pas parlé de Plan de Continuité d’Activité.
En gros tout est déjà prêt au cas où le site principal est HS. Voir, ça bascule tout seul, mais ça, c’est une autre histoire et ne coûte PAS DU TOUT la même chose qu’un PRA…
Et toi ? Tu gères tout ça comment ?
Crédit des images : Cartouche de sauvegarde DC Feu

Vous connaissez Terraform ? Et Let’s Encrypt ?
Le premier permet de gérer le cycle de vie de vos infrastructures. Et le deuxième vous permet d’avoir des certificats TLS signés par une autorité de certification valide dans les navigateurs modernes.
Dans cet article, je vais vous montrer comment faire un certificat wildcard *.mondomaine.tld avec Terraform. La validation avec le protocole acme la plus simple pour ces certificats, est via le DNS. Pour cela, je vais utiliser OVH puisque mon domaine de test est là-bas.
Bon, ce n’est pas le tout. Mais on n’est pas là pour beurrer des toasts.
Déjà, on va récupérer le repository qui permet de faire tout ça :
$ git clone https://github.com/nledez/hashicorppourlesnoobs-sources.git demo-terraform-le
$ cd demo-terraform-le/terraform
$ ls
install.sh ovh-create-consumer-key.py terraform.tf variables.tf
lecertificate.tf requirements.txt terraform.tfvarsÀ quoi servent ces fichiers ?
install.sh -> installer les dépendancesrequirements.txt -> les dépendances Pythonovh-create-consumer-key.py -> un script pour simplifier la création du fichier ovh.conf et ovh.shterraform.tf -> fichier Terraform qui définit la configuration des providers Terraformvariables.tf -> fichier Terraform qui définit les variables que l’on va utiliser dans notre plan Terraformterraform.tfvars -> fichier Terraform qui donne les valeurs que l’on va utiliser pour générer notre certificat TLSlecertificate.tf -> le plan Terraform qui permet de générer le certificat TLS chez Let’s EncryptOn va commencer par installer les prérequis pour causer avec l’API OVH et donc le DNS :
$ ./install.sh
Running virtualenv with interpreter /usr/local/bin/python3
Already using interpreter /usr/local/opt/python/bin/python3.7
Using base prefix '/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7'
New python executable in /private/tmp/demo-terraform-le/terraform/.venv/bin/python3.7
Also creating executable in /private/tmp/demo-terraform-le/terraform/.venv/bin/python
Installing setuptools, pip, wheel...
done.
Collecting ovh==0.5.0 (from -r requirements.txt (line 1))
Using cached https://files.pythonhosted.org/packages/58/92/db708f5a2e105ca48da1ac065c0168c7626685f9ab3667184dc2d9772bb1/ovh-0.5.0-py2.py3-none-any.whl
Installing collected packages: ovh
Successfully installed ovh-0.5.0Ça crée un virtualenv Python pour éviter de pourrir votre machine avec des libs Python. Puis installe la bibliothèque ovh.
Maintenant, on va créer le fichier ovh.conf
$ . ./.venv/bin/activate
$ ./ovh-create-consumer-key.py
Need to generate default ovh.conf
Go to https://eu.api.ovh.com/createApp/
Fill form get values and fill ovh.conf with:
application_key =
application_secret =
Now I quitComme l’indique le script, il faut aller sur la page https://eu.api.ovh.com/createApp/ pour créer le couple application_key & application_secret`.
On va rentrer son login/pass OVH puis un nom d’application et une description :

Une fois les application key/secret générées on va les copier pour les mettre dans le fichier ovh.conf.

$ vi ovh.conf
application_key = XXXX
application_secret = XXXXOn relance le script qui va vérifier que le consumer_key est encore valide. Et s’il n’est plus valide ou absent va nous en générer un nouveau.
$ ./ovh-create-consumer-key.py
Need a valid consumer_key
Please visit https://eu.api.ovh.com/auth/?credentialToken=XXXXXXXX to authenticate
and press Enter to continue...Là, soit on est sur un Mac, et ça ouvre automatiquement le lien. Sinon, il faut copier le lien et se connecter pour générer la consumer_key.
On rentre son login/pass, puis valide.

On peut fermer cette page, puis retourner sur le terminal.

Une fois fait, appuyer sur entrée.
Welcome Nicolas
Btw, your "consumerKey" is "XXXXX"Le script nous a rendu la main, et a automatiquement mis à jour le fichier ovh.conf.
$ cat ovh.conf
[default]
endpoint = ovh-eu
[ovh-eu]
application_key = XXX
application_secret = XXX
consumer_key = XXXOn peut même vérifier que le consumer_key est toujours valide :
$ ./ovh-create-consumer-key.py
Welcome Nicolas nothing to doOn va charger les variables d’environnement qui vont être utilisées par Terraform. Mais aussi éditer le fichier terraform.tfvars pour y mettre les bonnes valeurs.
$ . ./ovh.sh
$ vi terraform.tfvarsparent_domain -> le nom de domaine racine pour le certificat. Dans mon cas hashicorp4noobs.frsubdomain -> le sous-domaine pour mon wildcard. Je vais mettre tools. Au final, mon certificat sera sous la forme *.tools.hashicorp4noobs.frle_email -> l’email auquel Let’s Encrypt va envoyer les messages quand le certificat va expirerOn va en profiter pour éditer le fichier terraform.tf et utiliser le serveur de staging Let’s Encrypt (server_url = "https://acme-staging-v02.api.letsencrypt.org/directory")
$ vi terraform.tfEt il est maintenant temps d’initialiser Terraform :
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "tls" (hashicorp/tls) 2.1.1...
- Downloading plugin for provider "acme" (terraform-providers/acme) 1.4.0...
- Downloading plugin for provider "local" (hashicorp/local) 1.4.0...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.acme: version = "~> 1.4"
* provider.local: version = "~> 1.4"
* provider.tls: version = "~> 2.1"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.Tout s’est bien passé, on va attaquer la suite :
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# acme_certificate.certificate will be created
+ resource "acme_certificate" "certificate" {
+ account_key_pem = (sensitive value)
+ certificate_domain = (known after apply)
+ certificate_p12 = (sensitive value)
+ certificate_pem = (known after apply)
+ certificate_url = (known after apply)
+ common_name = "*.tools.hashicorp4noobs.fr"
+ id = (known after apply)
+ issuer_pem = (known after apply)
+ key_type = "2048"
+ min_days_remaining = 30
+ must_staple = false
+ private_key_pem = (sensitive value)
+ subject_alternative_names = [
+ "tools.hashicorp4noobs.fr",
]
+ dns_challenge {
+ provider = "ovh"
}
}
# acme_registration.local_registration will be created
+ resource "acme_registration" "local_registration" {
+ account_key_pem = (sensitive value)
+ email_address = "letsencrypt@hashicorp4noobs.fr"
+ id = (known after apply)
+ registration_url = (known after apply)
}
# local_file.le_tls_certificate will be created
+ resource "local_file" "le_tls_certificate" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0644"
+ filename = "le_tls_certificate.pem"
+ id = (known after apply)
}
# local_file.le_tls_chain_certificate will be created
+ resource "local_file" "le_tls_chain_certificate" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0644"
+ filename = "le_tls_chain_certificate.pem"
+ id = (known after apply)
}
# local_file.le_tls_issuer_certificate will be created
+ resource "local_file" "le_tls_issuer_certificate" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0644"
+ filename = "le_tls_issuer_certificate.pem"
+ id = (known after apply)
}
# local_file.le_tls_private_key will be created
+ resource "local_file" "le_tls_private_key" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0600"
+ filename = "le_tls_private_key.pem"
+ id = (known after apply)
}
# local_file.le_tls_private_key_file will be created
+ resource "local_file" "le_tls_private_key_file" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./le_account.key"
+ id = (known after apply)
}
# tls_private_key.le_tls_private_key will be created
+ resource "tls_private_key" "le_tls_private_key" {
+ algorithm = "RSA"
+ ecdsa_curve = "P224"
+ id = (known after apply)
+ private_key_pem = (sensitive value)
+ public_key_fingerprint_md5 = (known after apply)
+ public_key_openssh = (known after apply)
+ public_key_pem = (known after apply)
+ rsa_bits = 4096
}
Plan: 8 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:On tape yes puis entrée.
tls_private_key.le_tls_private_key: Creating...
tls_private_key.le_tls_private_key: Creation complete after 1s [id=517c54bf80a1bf7d67506ad2a8c5bcadc6b80749]
acme_registration.local_registration: Creating...
local_file.le_tls_private_key_file: Creating...
local_file.le_tls_private_key_file: Creation complete after 0s [id=11d0b10f636c0e08e8bf51722544cb1242c1b4c3]
acme_registration.local_registration: Creation complete after 3s [id=https://acme-staging-v02.api.letsencrypt.org/acme/acct/11286586]
acme_certificate.certificate: Creating...
acme_certificate.certificate: Still creating... [10s elapsed]
acme_certificate.certificate: Still creating... [20s elapsed]
acme_certificate.certificate: Still creating... [30s elapsed]
acme_certificate.certificate: Creation complete after 33s [id=https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa901221714abb3504588ce806a6b9838105]
local_file.le_tls_issuer_certificate: Creating...
local_file.le_tls_certificate: Creating...
local_file.le_tls_chain_certificate: Creating...
local_file.le_tls_private_key: Creating...
local_file.le_tls_issuer_certificate: Creation complete after 0s [id=b96fb4b4081d20c510fb1c6daa2256645cbc3c85]
local_file.le_tls_chain_certificate: Creation complete after 0s [id=a265e33f4ed96a3043e97f4a6f7356ce98877c09]
local_file.le_tls_private_key: Creation complete after 0s [id=387b4c339cde6e90d85143b11f0522435a03f174]
local_file.le_tls_certificate: Creation complete after 0s [id=2f0c5d9161d44617802561a2a19cccff506492af]
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.Et voilà, tout s’est bien passé. On peut vérifier que les fichiers sont bien là :
$ ls -1rt | grep '^le'
lecertificate.tf
le_account.key
le_tls_certificate.pem
le_tls_private_key.pem
le_tls_issuer_certificate.pem
le_tls_chain_certificate.pemComme c’était un test, on peut tout supprimer :
$ terraform destroy
tls_private_key.le_tls_private_key: Refreshing state... [id=517c54bf80a1bf7d67506ad2a8c5bcadc6b80749]
local_file.le_tls_private_key_file: Refreshing state... [id=11d0b10f636c0e08e8bf51722544cb1242c1b4c3]
acme_registration.local_registration: Refreshing state... [id=https://acme-staging-v02.api.letsencrypt.org/acme/acct/11286586]
acme_certificate.certificate: Refreshing state... [id=https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa901221714abb3504588ce806a6b9838105]
local_file.le_tls_issuer_certificate: Refreshing state... [id=b96fb4b4081d20c510fb1c6daa2256645cbc3c85]
local_file.le_tls_certificate: Refreshing state... [id=2f0c5d9161d44617802561a2a19cccff506492af]
local_file.le_tls_private_key: Refreshing state... [id=387b4c339cde6e90d85143b11f0522435a03f174]
local_file.le_tls_chain_certificate: Refreshing state... [id=a265e33f4ed96a3043e97f4a6f7356ce98877c09]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# acme_certificate.certificate will be destroyed
- resource "acme_certificate" "certificate" {
- account_key_pem = (sensitive value)
- certificate_domain = "*.tools.hashicorp4noobs.fr" -> null
- certificate_p12 = (sensitive value)
- certificate_pem = "-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n" -> null
- certificate_url = "https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa901221714abb3504588ce806a6b9838105" -> null
- common_name = "*.tools.hashicorp4noobs.fr" -> null
- id = "https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa901221714abb3504588ce806a6b9838105" -> null
- issuer_pem = "-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n" -> null
- key_type = "2048" -> null
- min_days_remaining = 30 -> null
- must_staple = false -> null
- private_key_pem = (sensitive value)
- subject_alternative_names = [
- "tools.hashicorp4noobs.fr",
] -> null
- dns_challenge {
- config = (sensitive value)
- provider = "ovh" -> null
}
}
# acme_registration.local_registration will be destroyed
- resource "acme_registration" "local_registration" {
- account_key_pem = (sensitive value)
- email_address = "letsencrypt@hashicorp4noobs.fr" -> null
- id = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/XXXX" -> null
- registration_url = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/XXXX" -> null
}
# local_file.le_tls_certificate will be destroyed
- resource "local_file" "le_tls_certificate" {
- content = "-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n" -> null
- directory_permission = "0777" -> null
- file_permission = "0644" -> null
- filename = "le_tls_certificate.pem" -> null
- id = "XXX" -> null
}
# local_file.le_tls_chain_certificate will be destroyed
- resource "local_file" "le_tls_chain_certificate" {
- content = "-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n" -> null
- directory_permission = "0777" -> null
- file_permission = "0644" -> null
- filename = "le_tls_chain_certificate.pem" -> null
- id = "XXX" -> null
}
# local_file.le_tls_issuer_certificate will be destroyed
- resource "local_file" "le_tls_issuer_certificate" {
- content = "-----BEGIN CERTIFICATE-----\n[...]\n-----END CERTIFICATE-----\n" -> null
- directory_permission = "0777" -> null
- file_permission = "0644" -> null
- filename = "le_tls_issuer_certificate.pem" -> null
- id = "XXX" -> null
}
# local_file.le_tls_private_key will be destroyed
- resource "local_file" "le_tls_private_key" {
- content = "-----BEGIN RSA PRIVATE KEY-----\n[...]\n-----END RSA PRIVATE KEY-----\n" -> null
- directory_permission = "0777" -> null
- file_permission = "0600" -> null
- filename = "le_tls_private_key.pem" -> null
- id = "XXX" -> null
}
# local_file.le_tls_private_key_file will be destroyed
- resource "local_file" "le_tls_private_key_file" {
- content = "-----BEGIN RSA PRIVATE KEY-----\n[...]\n-----END RSA PRIVATE KEY-----\n" -> null
- directory_permission = "0777" -> null
- file_permission = "0777" -> null
- filename = "./le_account.key" -> null
- id = "XXX" -> null
}
# tls_private_key.le_tls_private_key will be destroyed
- resource "tls_private_key" "le_tls_private_key" {
- algorithm = "RSA" -> null
- ecdsa_curve = "P224" -> null
- id = "XXX" -> null
- private_key_pem = (sensitive value)
- public_key_fingerprint_md5 = "XXX" -> null
- public_key_openssh = "XXX" -> null
- public_key_pem = "-----BEGIN PUBLIC KEY-----\n[...]\n-----END PUBLIC KEY-----\n" -> null
- rsa_bits = 4096 -> null
}
Plan: 0 to add, 0 to change, 8 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
local_file.le_tls_issuer_certificate: Destroying... [id=b96fb4b4081d20c510fb1c6daa2256645cbc3c85]
local_file.le_tls_chain_certificate: Destroying... [id=a265e33f4ed96a3043e97f4a6f7356ce98877c09]
local_file.le_tls_private_key_file: Destroying... [id=11d0b10f636c0e08e8bf51722544cb1242c1b4c3]
local_file.le_tls_private_key: Destroying... [id=387b4c339cde6e90d85143b11f0522435a03f174]
local_file.le_tls_certificate: Destroying... [id=2f0c5d9161d44617802561a2a19cccff506492af]
local_file.le_tls_private_key_file: Destruction complete after 0s
local_file.le_tls_certificate: Destruction complete after 0s
local_file.le_tls_private_key: Destruction complete after 0s
local_file.le_tls_issuer_certificate: Destruction complete after 0s
local_file.le_tls_chain_certificate: Destruction complete after 0s
acme_certificate.certificate: Destroying... [id=https://acme-staging-v02.api.letsencrypt.org/acme/cert/fa901221714abb3504588ce806a6b9838105]
acme_certificate.certificate: Destruction complete after 2s
acme_registration.local_registration: Destroying... [id=https://acme-staging-v02.api.letsencrypt.org/acme/acct/11286586]
acme_registration.local_registration: Destruction complete after 1s
tls_private_key.le_tls_private_key: Destroying... [id=517c54bf80a1bf7d67506ad2a8c5bcadc6b80749]
tls_private_key.le_tls_private_key: Destruction complete after 0s
Destroy complete! Resources: 8 destroyed.Et voilà, vous savez :
Si votre DNS n’est pas chez OVH, vous pouvez changer le dns_challenge provider = "ovh" dans le fichier lecertificate.tf.
Voici la liste des providers DNS ainsi que leurs documentations respectives.

Il y a un dicton que j’aime bien :
Dans la vie, il y a deux types d’administrateurs système :
- Celui qui a déjà fait une grosse connerie en production
- Et celui que ne va pas tarder
Et bien je viens de passer de la deuxième à la première catégorie en une ligne de commande :
cd /var/lib/mysql
rm -rf *Et bien entendu sur le serveur de production, et pas sur la future prod.
Bref, la GROSSE boulette. Sur une BDD de 80Go qui prend plusieurs heures à restaurer.
Et bien gros spoiler, nous avons réussi à tout restaurer.
Avant de lire la suite, je ne garantis pas que cela va fonctionner chez vous. Et décline toute responsabilité si vous perdez vos données.
Mais vous serez peut-être content de pouvoir récupéré un maximum de vos bases/tables.
Déjà, le premier truc à faire, c’est couper toute activité qu’il pourrait y avoir sur cette instance :
Peut importe, mais surtout réduire les accès à cette instance.
Le deuxième truc à avoir en tête, NE PAS ARRÊTER Mysql. C’est ce qui va nous sauver.
J’ai depuis écrit un script Python qui va permettre de sauvegarder un maximum de chose.
Ensuite, je vais rapidement expliquer pourquoi tout cela peut fonctionner.
Mais d’abord, voici comment reproduire ça chez vous (dans un Vagant). N’essayez pas ça sur un vrai serveur.
On va utiliser Vagrant
git clone https://github.com/nledez/recover_deleted_mysql.git
cd recover_deleted_mysql
vagrant up
vagrant ssh
# À partir de là, nous sommes dans la VM Vagrant.
# Avant de faire des opérations dangereuses, assurez-vous de toujours y être...
# Le prompt doit être : vagrant@ubuntu-bionic
# On va charger des données dans Mysql
git clone https://github.com/datacharmer/test_db.git
cd test_db
cat employees.sql | sudo mysql
# On affiche la liste de bases pour vérifier que l'import à bien fonctionné
echo 'show databases;' | sudo mysql
# On va faire un backup pour plus tard (il en faut toujours un, et ça va permettre de redémarrer le test à partir de là)
sudo service mysql stop
sudo mkdir /backup
sudo rsync -av /var/lib/mysql/ /backup/
sudo chmod 777 /backup
sudo service mysql start
sudo bash /vagrant/read_all_data.sh
# La dernière commande permet de lister toutes les données de toutes les tables, ça permet de tout charger en mémoire.
# Il est temps de détruire la BDD (C'est surtout cette partie là à ne jamais faire en prod)
sudo find /var/lib/mysql -type f -exec rm {} \;
sudo find /var/lib/mysql -type d -exec rmdir {} \;
# Ouupppsss
sudo ls -la /var/lib/mysql/
# Et maintenant, on va vérifier que c'est bien la grosse merde en prod :
echo 'show databases;' | sudo mysql# On crée un répertoire dans lequel on va mettre les fichiers que l'on va récupérer (il faudra la place de l'ancien /var/lib/mysql)
sudo mkdir /recover
# On va récupérer le PID du process Mysql
ps ax | grep [m]ysqld
# On va le stocker dans une variable (c'est plus simple pour le reste de l'article)
MYSQL_PID=2530
# On va faire un `lsof` qui va nous montrer pourquoi on va pouvoir récupérer la catastrophe
sudo lsof -p $MYSQL_PID--help$ sudo /vagrant/recover_deleted_mysql.py --help
usage: recover_deleted_mysql.py [-h] --pid PID [--recover_path RECOVER_PATH]
[--mysql_path MYSQL_PATH] [--touch_files]
[--export_as_csv EXPORT_AS_CSV [EXPORT_AS_CSV ...]]
[--csv_path CSV_PATH]
Mysql recuperator
optional arguments:
-h, --help show this help message and exit
--pid PID PID of Mysql process
--recover_path RECOVER_PATH
Path of directory if you want revover deleted files
--mysql_path MYSQL_PATH
Path of mysql directory if you want limit recovery
--touch_files If you want touch deleted files
--export_as_csv EXPORT_AS_CSV [EXPORT_AS_CSV ...]
List of databases to export require --csv_path
argument
--csv_path CSV_PATH Path of csv exportsudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --mysql_path /var/lib/mysql --recover_path /recover
# Et on peux vérifier avec la commande `sudo find /recover -ls`Bon, si jamais la BDD est corrompue, on sera bien content d’avoir les données au format CSV. Par contre, dans mes tests, j’ai eu beaucoup de crash du moteur Mysql, il faut donc être très prudent dans les commandes. Mais aussi se dire que ça risque de planter et que l’on va tout perdre.
# Première étape, on va faire un touch des fichiers qui ont été supprimés (oui, ca parait débile, mais pourtant ça fonctionne)
sudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --mysql_path /var/lib/mysql --touch_files
# TRÈS important, changer les droits des répertoires et fichiers avant de faire quoi que ce soit (c'est là que Mysql peut planter)
sudo chown -R mysql:mysql /var/lib/mysql
# Maintenant, on peut lister les BDD
echo 'show databases;' | sudo mysql
# Mais on ne voit pas encore les tables
echo 'show tables;' | sudo mysql employees
# Il nous faut les fichiers .frm qui contiennent les structures des tables (il faut donc avoir un backup de ces fichiers sous la main)
cd /backup/
sudo find * -name '*.frm' -exec cp {} /var/lib/mysql/{} \;
sudo chown -R mysql:mysql /var/lib/mysql
echo 'show tables;' | sudo mysql employees
# Et maintenant, on peut faire des select dans les tables. Mais pas de mysqldump et surtout dans mes tests, ça a fait cracher Mysql :(
# NE PAS LANCER de mysqldump avant d'avoir terminé tout ça
# Dans une installation "normal", Mysql ne peut pas écrire de fichier n'importe où. On va aller chercher le chemin qui est configuré :
echo 'SHOW VARIABLES LIKE "secure_file_priv";' | sudo mysql # Get /var/lib/mysql-files/
# Et on lance l'export
sudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --csv_path /var/lib/mysql-files --export_as_csv employeesEt voilà !
# On copie les fichiers restaurés dans le répertoire Mysql :
sudo rsync -av /recover/var/lib/mysql/ /var/lib/mysql/
# On arrête Mysql
sudo service mysql stop
# On remet les bons droits
sudo chown -R mysql:mysql /var/lib/mysql
# On relance Mysql
sudo service mysql start
# On demande à Mysql de faire un check des bases
sudo mysqlcheck --all-databases
# On lance un `mysql_upgrade` qui remet toutes les bases/tables système en état de marche
sudo mysql_upgrade
# On relance Mysql
sudo service mysql restart
# On relance un Mysqldump pour être bien sûr que la BDD refonctionne bien
cd
sudo mysqldump employees > employees.sqlps ax | grep [m]ysqldsudo lsof -p $MYSQL_PIDDéjà, il faut comprendre comment fonctionne Linux en ext* au moment de la suppression d’un fichier.
On va revenir à la base. Quand un process ouvre un fichier, il ouvre un “file descriptor” FD dans lsof.
Un fichier, c’est défini dans le système de fichier ext* comme un chemin qui pointe vers un inode.
Quand on supprime un fichier, on supprime ce lien dans la table d’allocation des fichiers.
Et ext* libère la place quand un inode n’a plus de FD ouvert sur l’inode.
C’est pour ça que des fois, on supprime un gros fichier, mais que la place n’est pas libérée.
Ou encore quand on fait un mv d’un fichier de log, un touch du nouveau fichier et que le process continue à écrire dans le fichier qui a été renommé. Le FD est toujours ouvert sur l’ancien inode. Ça fait un peu mal à la tête hein ?
Et donc comme Mysql garde les fichiers ouverts, on peut faire un cat /proc/$PID/df/9
Voilà pour la partie récupération des fichiers supprimés.
Maintenant, les histoires de touch.
C’est la partie que je maîtrise moins, mais j’imagine le comportement de Mysql.
Quand on fait un show databases; Mysql doit lire la liste des répertoires dans /var/lib/mysql. C’est pour ça que l’on peut se retrouver avec une base de donnés lost+found.
Ensuite quand on fait un show tables; Mysql doit lire la liste des fichiers *.MYI ou *.MYD (ou les deux, ou quand l’un manque ça doit planter). Donc un touch des fichiers fait apparaître les tables. Mais comme les fichiers sont déjà ouverts, il va pouvoir accéder aux contenu des anciens fichiers supprimés.
Par contre un desc table ou un select ne va pas fonctionner. Pour cela Mysql a besoin des fichiers *.frm qui contiennent la structure des tables. Et là, il faut que les fichiers frm/MYD/MYI soient bien alignés.
Voilà, il ne nous reste que le Mysqldump qui ne fonctionne pas. Je pense qu’il essaye de lire directement dans le contenu des fichiers et que la bidouille avec les touch le perturbe.
J’en ai fini pour cet article. J’espère vous avoir appris des trucs. Que vous n’aurez jamais besoin de mettre tout ça en pratique sur de la prod.
Et si j’ai des conseils à donner :

There’s a saying I like:
In life, there are two types of system administrators: The one who has already made a big mistake in production And the one that will soon be
Well, I just moved from the second to the first category in a command line:
cd /var/lib/mysql
rm -rf *And of course on the production server, and not on the future one.
In short, the BIG dumpling. On an 80GB database that takes several hours to restore.
And very big spoiler, we managed to restore everything.
Before I read on, I don’t guarantee that this will work for you. And declines any responsibility if you lose your data.
But you may be happy to be able to get as much of your bases/tables as possible.
First of all, the first thing to do is to cut off any activity that may be present on this instance:
Whatever, but above all, reduce access to this instance.
The second thing to keep in mind, DO NOT STOP Mysql. That’s what’s going to save us.
I have since written a Python script that will allow me to save as much as possible.
Then I will quickly explain why all this can work.
But first, here’s how to reproduce this in your home (in a Vagant). Don’t try that on a real server.
We’re going to use Vagrant
git clone https://github.com/nledez/recover_deleted_mysql.git
cd recover_deleted_mysql
vagrant up
vagrant ssh
# From there, we are in the Vagrant VM.
# Before doing dangerous operations, make sure you are always there...
# The prompt must be: vagrant@ubuntu-bionic
# We're going to load test data into Mysql
git clone https://github.com/datacharmer/test_db.git
cd test_db
cat employees.sql | sudo mysql
# We display the list of bases to check that the import has worked well
echo 'show databases;' | sudo mysql
# We'll make a backup for later (you still need one, and it will allow you to restart the test from there)
sudo service mysql stop
sudo mkdir /backup
sudo rsync -av /var/lib/mysql/ /backup/
sudo chmod 777 /backup
sudo service mysql start
sudo bash /vagrant/read_all_data.sh
# The last command lists all the data from all the tables, so that everything can be loaded into memory.
# It's time to destroy all database files (it's especially that part you never do in production)
sudo find /var/lib/mysql -type f -exec rm {} \;
sudo find /var/lib/mysql -type d -exec rmdir {} \;
# Ouupppsss
sudo ls -la /var/lib/mysql/
# And now we're going to check that it's the big shit in production:
echo 'show databases;' | sudo mysql# We create a directory in which we will put the files we will recover (we will need the place of the old /var/lib/mysql)
sudo mkdir /recover
# We're going to get the PID from the Mysql process
ps ax | grep [m]ysqld
# We will store it in a variable (it's easier for the rest of the article)
MYSQL_PID=2530
# We're going to do a `lsof` that will show us why we're going to be able to recover the disaster
sudo lsof -p $MYSQL_PID--help mode$ sudo /vagrant/recover_deleted_mysql.py --help
usage: recover_deleted_mysql.py [-h] --pid PID [--recover_path RECOVER_PATH]
[--mysql_path MYSQL_PATH] [--touch_files]
[--export_as_csv EXPORT_AS_CSV [EXPORT_AS_CSV ...]]
[--csv_path CSV_PATH]
Mysql recuperator
optional arguments:
-h, --help show this help message and exit
--pid PID PID of Mysql process
--recover_path RECOVER_PATH
Path of directory if you want revover deleted files
--mysql_path MYSQL_PATH
Path of mysql directory if you want limit recovery
--touch_files If you want touch deleted files
--export_as_csv EXPORT_AS_CSV [EXPORT_AS_CSV ...]
List of databases to export require --csv_path
argument
--csv_path CSV_PATH Path of csv exportsudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --mysql_path /var/lib/mysql --recover_path /recover
# And we can check with the command `sudo find /recover -ls`Well, if the database is ever corrupted, we’ll be happy to have the data in CSV format. However, in my tests, I had a lot of crashes with the Mysql engine, so you have to be very careful with the controls. But also to think that it might crash and that we’ll lose everything.
# First step, we'll do a touch of the files that have been deleted (yes, it sounds stupid, but it works)
sudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --mysql_path /var/lib/mysql --touch_files
# VERY important, change the permissions of directories and files before doing anything (this is where Mysql can crash)
sudo chown -R mysql:mysql /var/lib/mysql
# Now, we can list the DBs
echo 'show databases;' | sudo mysql
# But we don't see the tables yet
echo 'show tables;' | sudo mysql employees
# We need the.frm files that contain the table structures (so we need a backup of these files on hand)
cd /backup/
sudo find * -name '*.frm' -exec cp {} /var/lib/mysql/{} \;
sudo chown -R mysql:mysql /var/lib/mysql
echo 'show tables;' | sudo mysql employees
# And now we can make selections in the tables. But no mysqldump and especially in my tests, it made Mysql crash :(
# DO NOT start mysqldump until you have finished all this
# In a "normal" installation, Mysql cannot write a file anywhere. We're going to look for the path that's configured:
echo 'SHOW VARIABLES LIKE "secure_file_priv";' | sudo mysql # Get /var/lib/mysql-files/
# And we start exporting
sudo /vagrant/recover_deleted_mysql.py --pid $MYSQL_PID --csv_path /var/lib/mysql-files --export_as_csv employeesAnd voilà!
# Restored files are copied to the Mysql directory:
sudo rsync -av /recover/var/lib/mysql/ /var/lib/mysql/
# We're arresting Mysql
sudo service mysql stop
# We give the right rights back
sudo chown -R mysql:mysql /var/lib/mysql
# We're relaunching Mysql
sudo service mysql start
# We're asking Mysql to do a check of the databases
sudo mysqlcheck --all-databases
# Launch a `mysql_upgrade` that restores all system bases/tables to working order
sudo mysql_upgrade
# We're relaunching Mysql
sudo service mysql restart
# We restart a Mysqldump to make sure that the database works again well
cd
sudo mysqldump employees > employees.sqlps ax | grep[m]ysqldsudo lsof -p $MYSQL_PIDAlready, it is necessary to understand how Linux works in ext* when deleting a file.
We’re going back to the base. When a process opens a file, it opens an FD “file descriptor” in lsof.
A file is defined in the ext* file system as a path that points to an inode.
When you delete a file, you delete this link in the file allocation table.
And ext* frees up the space when an inode no longer has an FD open on the inode.
That’s why sometimes you delete a large file, but the space is not cleared.
Or when you make a `mv’ of a log file, touch the new file and the process continues to write to the file that has been renamed. The FD is still open on the old inode. It hurts a little bit of a headache, doesn’t it?
And so since Mysql keeps the files open, we can make a cat /proc/$PID/df/9.
So much for the recovery part of the deleted files.
Now, the stories around touch.
This is the part I have less control over, but I can imagine Mysql’s behavior.
When doing a show databases; Mysql must read the list of directories in /var/lib/mysql. That’s why you can end up with a lost+found database.
Then when we do a show tables; Mysql must read the list of files *.MYI or *.MYD (or both, or when one misses mysql may be crash). So a touch of the files brings up the tables. But since the files are already open, he will be able to access the old deleted files content.
However, a desc table or select will not work. For this purpose Mysql needs the *.frm files that contain the table structure. And here, the frm/MYD/MYI files must be properly aligned.
That’s it, we only have the Mysqldump that doesn’t work. I think he tries to read directly into the content of the files and that the `touch’ mess disturbs him.
I’m done with this article. I hope I taught you some things. That you’ll never have to put all this into practice on production.
And if I have any advice to give:

Serveur iPXE ou comment booter sans USB/CD/whatever
Avant, quand je voulais (ré)installer une machine, je gravais un CD (bien personnalisé à coup de mkisofs). Ensuite, je suis passé aux clés USB.
Mais c’est toujours pénible :
Bref, ce n’est pas pratique.
On va donc passer à quelque chose de moderne comme (i)PXE.
Un peu de lecture pour comprendre ce que c’est :
Définition Wikipedia de Preboot Execution Environment.
TL;DR donc, pour résumer c’est :
Ça, c’était bien quand j’avais un NAS avec tout ce qu’il fallait comme stockage. Mais je suis passé sur un APU2 avec OpenSense. Je suis donc passé de 6To de stockage à 13Go en SDD. Par contre, je suis aussi passé d’un ASDL faiblar à de la fibre qui met le datacenter d’OVH dans mon salon.
Il me fallait donc une solution pour booter à partir d’une machine sur Internet.
iPXE arrive à la rescousse. C’est une version open source (c’est un point positif) de firmware PXE. Mais surtout avec beaucoup plus de fonctionnalités (et c’est là que ça devient intéressant). Comme la possibilité de télécharger les images en HTTP à la place de TFTP.
Par contre, le firmware n’est pas forcément disponible dans les cartes réseau communes.
Ça tombe bien, on peut faire une image compatible PXE pour iPXE (Upsa powered).
Si on reprend la suite de tout à l’heure :
Il te faudra donc :
Alors, je dis OpenSense, et le reste de l’article est basé dessus. Mais ça fonctionnera parfaitement avec PFSense, DD-WRT, un Linux installé à la “main”.
Pour générer l’image iPXE, 2 solutions :
J’ai passé pas mal de temps a essayer de faire fonctionner les images générées par rom-o-matic. Mais c’était pénible de remettre la configuration, attendre la génération sur le serveur. Et puis, c’est cool de leur libérer un peu de CPU.
Mais tu peux facilement reproduire la configuration ci-dessous dans les paramètres ROM-o-Matic. Mais c’est beaucoup moins marrant :)
Bon. On y va ? Ou bien ?
Tu fais comme tu veux. Mais moi, je mets Nginx comme serveur HTTP. Voici la configuration du virtualhost à mettre :
server{
listen 80;
listen [::]:80;
server_name ipxe.example.com pxe.example.com;
root /var/www/ipxe;
index index.html index.htm;
}Tu vas maintenant installer tous les fichiers nécessaires à la partie iPXE :
cd /var/www
git clone https://github.com/nledez/ipxe-root ipxe
cd /var/www/ipxe
sed 's@%BOOT_URL%@http://ipxe.example.com/@;s@http://boot.smidsrod.lan/@http://ipxe.example.com/@;s@sysrcd-version 3.8.0@sysrcd-version 5.2.2@' boot.ipxe.cfg.example
mkdir boot
touch boot/.bootdir
./install.sh bootcd /var/www/ipxe
./install-ubuntu.shcd /var/www/ipxe
git clone git://git.ipxe.org/ipxe.git ipxe
cd ipxe/src
cat > myscript.ipxe <<EOF
#!ipxe
dhcp
chain http://ipxe.example.com/boot.ipxe
EOF
vi config/general.hC’est là, ou ça prend du temps pour avoir tous les bons paramètres fonctionnels. Voilà un patch de git vs ma configuration :
diff --git a/src/config/general.h b/src/config/general.h
index 3c14a2cd..75f3a432 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -36,16 +36,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define NET_PROTO_IPV4 /* IPv4 protocol */
#undef NET_PROTO_IPV6 /* IPv6 protocol */
-#undef NET_PROTO_FCOE /* Fibre Channel over Ethernet protocol */
-#define NET_PROTO_STP /* Spanning Tree protocol */
-#define NET_PROTO_LACP /* Link Aggregation control protocol */
+//#undef NET_PROTO_FCOE /* Fibre Channel over Ethernet protocol */
+//#define NET_PROTO_STP /* Spanning Tree protocol */
+//#define NET_PROTO_LACP /* Link Aggregation control protocol */
/*
* PXE support
*
*/
//#undef PXE_STACK /* PXE stack in iPXE - you want this! */
-//#undef PXE_MENU /* PXE menu booting */
+#undef PXE_MENU /* PXE menu booting */
/*
* Download protocols
@@ -55,9 +55,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define DOWNLOAD_PROTO_TFTP /* Trivial File Transfer Protocol */
#define DOWNLOAD_PROTO_HTTP /* Hypertext Transfer Protocol */
#undef DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */
-#undef DOWNLOAD_PROTO_FTP /* File Transfer Protocol */
-#undef DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */
-#undef DOWNLOAD_PROTO_NFS /* Network File System Protocol */
+//#undef DOWNLOAD_PROTO_FTP /* File Transfer Protocol */
+//#undef DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */
+//#undef DOWNLOAD_PROTO_NFS /* Network File System Protocol */
//#undef DOWNLOAD_PROTO_FILE /* Local filesystem access */
/*
@@ -85,9 +85,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* 802.11 cryptosystems and handshaking protocols
*
*/
-#define CRYPTO_80211_WEP /* WEP encryption (deprecated and insecure!) */
-#define CRYPTO_80211_WPA /* WPA Personal, authenticating with passphrase */
-#define CRYPTO_80211_WPA2 /* Add support for stronger WPA cryptography */
+//#define CRYPTO_80211_WEP /* WEP encryption (deprecated and insecure!) */
+//#define CRYPTO_80211_WPA /* WPA Personal, authenticating with passphrase */
+//#define CRYPTO_80211_WPA2 /* Add support for stronger WPA cryptography */
/*
* Name resolution modules
@@ -103,16 +103,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* you want to use.
*
*/
-//#define IMAGE_NBI /* NBI image support */
-//#define IMAGE_ELF /* ELF image support */
-//#define IMAGE_MULTIBOOT /* MultiBoot image support */
-//#define IMAGE_PXE /* PXE image support */
-//#define IMAGE_SCRIPT /* iPXE script image support */
-//#define IMAGE_BZIMAGE /* Linux bzImage image support */
-//#define IMAGE_COMBOOT /* SYSLINUX COMBOOT image support */
+#define IMAGE_NBI /* NBI image support */
+#define IMAGE_ELF /* ELF image support */
+#define IMAGE_MULTIBOOT /* MultiBoot image support */
+#define IMAGE_PXE /* PXE image support */
+#define IMAGE_SCRIPT /* iPXE script image support */
+#define IMAGE_BZIMAGE /* Linux bzImage image support */
+#define IMAGE_COMBOOT /* SYSLINUX COMBOOT image support */
//#define IMAGE_EFI /* EFI image support */
-//#define IMAGE_SDI /* SDI image support */
-//#define IMAGE_PNM /* PNM image support */
+#define IMAGE_SDI /* SDI image support */
+#define IMAGE_PNM /* PNM image support */
#define IMAGE_PNG /* PNG image support */
#define IMAGE_DER /* DER image support */
#define IMAGE_PEM /* PEM image support */
@@ -136,24 +136,24 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define LOGIN_CMD /* Login command */
#define SYNC_CMD /* Sync command */
#define SHELL_CMD /* Shell command */
-//#define NSLOOKUP_CMD /* DNS resolving command */
-//#define TIME_CMD /* Time commands */
-//#define DIGEST_CMD /* Image crypto digest commands */
-//#define LOTEST_CMD /* Loopback testing commands */
+#define NSLOOKUP_CMD /* DNS resolving command */
+#define TIME_CMD /* Time commands */
+#define DIGEST_CMD /* Image crypto digest commands */
+#define LOTEST_CMD /* Loopback testing commands */
//#define VLAN_CMD /* VLAN commands */
-//#define PXE_CMD /* PXE commands */
-//#define REBOOT_CMD /* Reboot command */
-//#define POWEROFF_CMD /* Power off command */
-//#define IMAGE_TRUST_CMD /* Image trust management commands */
-//#define PCI_CMD /* PCI commands */
-//#define PARAM_CMD /* Form parameter commands */
-//#define NEIGHBOUR_CMD /* Neighbour management commands */
-//#define PING_CMD /* Ping command */
-//#define CONSOLE_CMD /* Console command */
-//#define IPSTAT_CMD /* IP statistics commands */
-//#define PROFSTAT_CMD /* Profiling commands */
-//#define NTP_CMD /* NTP commands */
-//#define CERT_CMD /* Certificate management commands */
+#define PXE_CMD /* PXE commands */
+#define REBOOT_CMD /* Reboot command */
+#define POWEROFF_CMD /* Power off command */
+#define IMAGE_TRUST_CMD /* Image trust management commands */
+#define PCI_CMD /* PCI commands */
+#define PARAM_CMD /* Form parameter commands */
+#define NEIGHBOUR_CMD /* Neighbour management commands */
+#define PING_CMD /* Ping command */
+#define CONSOLE_CMD /* Console command */
+#define IPSTAT_CMD /* IP statistics commands */
+#define PROFSTAT_CMD /* Profiling commands */
+#define NTP_CMD /* NTP commands */
+#define CERT_CMD /* Certificate management commands */
/*
* ROM-specific options
@@ -166,14 +166,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
* Virtual network devices
*
*/
-#define VNIC_IPOIB /* Infiniband IPoIB virtual NICs */
+//#define VNIC_IPOIB /* Infiniband IPoIB virtual NICs */
//#define VNIC_XSIGO /* Infiniband Xsigo virtual NICs */
/*
* Error message tables to include
*
*/
-#undef ERRMSG_80211 /* All 802.11 error descriptions (~3.3kb) */
+//#undef ERRMSG_80211 /* All 802.11 error descriptions (~3.3kb) */
/*
* Obscure configuration optionsMaintenant, tu vas pouvoir compiler tout ça :
/var/www/ipxe/ipxe/src# make bin/undionly.kpxe
[VERSION] bin/version.undionly.kpxe.o
[LD] bin/undionly.kpxe.tmp
[BIN] bin/undionly.kpxe.bin
[ZINFO] bin/undionly.kpxe.zinfo
[ZBIN] bin/undionly.kpxe.zbin
[FINISH] bin/undionly.kpxe
rm bin/version.undionly.kpxe.o bin/undionly.kpxe.zinfo bin/undionly.kpxe.bin bin/undionly.kpxe.zbinEt voilà, c’est terminé pour la partie iPXE.
On va commencer par DNSMasq pour la partie TFTP :

Dans Service / Dnsmasq DNS / Settings :
enable-tftp
tftp-root=/tftpClique sur “Save”. Et passes à Unbound :

Dans Service / DHCPv4 / LAN :
Et pour finir, tu vas copier le firmware que tu as construit tout à l’heure. Ouvre un shell (en ssh par exemple):
mkdir /tftp
cd /tftp/
curl http://ipxe.example.com/ipxe/src/bin/undionly.kpxe > undionly.kpxeEt voilà ce que ça donne :
Tu peux voir le boot PXE + iPXE avec Memtest86+ puis un début d’installation Ubuntu.
I already have a Domoticz install with Sonoff stuff & Tasmota firmware.
I manage power for:
But webcam doesn’t work during the night. I have an ANAVI Light Controller to fix this.
I want this in Domoticz:

If you want the same, you can do it with this configuration:
Open “Configuration” / “Configure module” it must look like this:

In Domoticz configure a MQTT Gateway, search on the Internet to find out how to do it.

Now click on “Create Virtual Sensors”

Now available in devices view:

Note the “Idx” for later and return to Tasmota configuration “Configure Domoticz” part:

Put the “Idx” from Domoticz into “Idx 1”.
Now you can change state and color in Domoticz:

And voilà!

Pour commencer à jouer un peu avec Consul, on va le faire tourner en local.
Mais aussi faire un premier tour d’horizon de ses paramètres.
Déjà il faut le binaire à prendre sur le Site de Consul.
Ensuite, je t’ai préparé quelques fichiers tout prêts pour t’éviter de tout copier/coller. C’est disponible ici.
git clone https://github.com/nledez/consul-screencast.git
cd consul-screencast
J’ai aussi mis un script pour se souvenir comment lancer Consul :
$ cat launch_consul.sh
#!/bin/bash
consul agent -config-dir=$(pwd)/config.d -dev
Comme tu dois t’en douter, les fichiers de configuration vont aller dans config.d :
mkdir config.d
cp ex.consul/* config.d
Voici le contenu de chaque fichier :
==== config.d/bootstrap.json
{
"bootstrap": true
}
==== config.d/server.json
{
"server": true,
"datacenter": "dc1",
"data_dir": "`(mkdir data && cd data && pwd)`",
"bind_addr": "127.0.0.1",
"start_join": ["127.0.0.1"],
"log_level": "INFO"
}
==== config.d/ui.json
{
"ui": true
}
À la ligne data_dir, on va remplacer ce qu’il y a entre les guillemets par le résultat de la commande :
`(mkdir data && cd data && pwd)`
Le paramètre bootstrap est à positionner sur un (uniquement un) des serveurs dans le cluster. C’est pour simplifier les élections. Quand les élections n’arrivent pas à se faire (coucou les Belges), celui-là est le dictateur et s’élit de “force”.
Il y a deux modes pour les services Consul :
Comme on l’a vu plus haut le binaire est lancé avec l’instruction agent. Mais c’est le paramètre server qui va indiquer que cet agent est en mode serveur.
On pourrait se passer de datacenter qui de toute façon est par défaut à dc1. Mais au moins les choses sont explicites. Consul est prévu pour fonctionner sur plusieurs data-centres. Ce paramètre permet donc de savoir où est situé cet agent.
On peut aussi démarrer Consul avec le paramètre -dev. Dans ce cas toutes les informations seront stockées en mémoire. Donc à chaque redémarrage, on repart à 0.
Le paramètre data_dir permet de spécifier où Consul va persister ses informations. Que ce soit pour un serveur ou un agent. Il est impératif de pouvoir faire des lock sur ces fichiers. Il faut donc se méfier des filesystem un peu space type Virtualbox/NFS/… Il est parfaitement logique qu’il faille absolument mettre ce paramètre sur les membres du cluster pour ne pas perdre les données.
Pour spécifier sur quelle interface réseau Consul va communiquer, on va utiliser bind_addr. Par défaut Consul utilise 0.0.0.0. Mais si plusieurs IPv4 sont présentes sur la machine Consul ne va pas démarrer. Ma machine et certains de mes serveurs ont plusieurs IPv4. Donc autant éviter les problèmes.
Il faut maintenant spécifier la liste des membres du cluster avec start_join. Ici on va mettre 127.0.0.1. C’est juste pour jouer en local.
Si tu as trop ou pas suffisamment de logs dans ton terminal, tu peux jouer avec log_level.
Et pour finir le paramètre ui indique à Consul de servir l’UI sur le port HTTP.
Il nous reste qu’à démarrer :
bash ./launch_consul.sh
# Ou :
consul agent -config-dir=$(pwd)/config.d
# Ou encore :
consul agent -config-dir=$(pwd)/config.d -dev
Ça va te mettre dans la console des trucs du style :
bootstrap = true: do not enable unless necessary
==> Starting Consul agent...
==> Joining cluster...
Join completed. Synced with 1 initial agents
==> Consul agent running!
Version: 'v1.0.2'
Node ID: 'e8b45893-f77f-09b2-3ce5-15397c3a39f6'
Node name: 'jerry'
Datacenter: 'dc1' (Segment: '')
Server: true (Bootstrap: true)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
==> Log data will now stream in as it occurs:
2018/02/06 22:58:27 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:e8b45893-f77f-09b2-3ce5-15397c3a39f6 Address:127.0.0.1:8300}]
[...]
En version commentée :
==> Starting Consul agent... -> Ben là ça démarre...
==> Joining cluster...
Join completed. Synced with 1 initial agents -> On a joint le cluster. On est synchro avec un agent (c'est lui même)
==> Consul agent running! -> L'agent fonctionne
Version: 'v1.0.2' -> En version 1.0.2
Node ID: 'e8b45893-f77f-09b2-3ce5-15397c3a39f6' -> L'ID du noeud
Node name: 'jerry' -> Son nom
Datacenter: 'dc1' (Segment: '') -> Il est dans le datacenter 'dc1' et tous les segments
Server: true (Bootstrap: true) -> C'est un serveur. Et même le dictateur
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, DNS: 8600) -> L'agent écoute sur 127.0.0.1
-> l'HTTPS est désactivé par défaut
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302) -> Ça, c'est pour que les membres du cluster communiquent
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false
-> On ne chiffre pas le protocole Gossip
-> On ne chiffre ni ne vérifie avec TLS en entrée/sortie
On peut aussi vérifier la liste de tous les membres :
$ consul members
Node Address Status Type Build Protocol DC Segment
jerry 127.0.0.1:8301 alive server 1.0.2 2 dc1
Mais aussi récupérer toutes les informations :
consul info
agent:
check_monitors = 0
check_ttls = 0
checks = 0
services = 0
build:
prerelease =
revision = b55059f+
version = 1.0.2
consul:
bootstrap = true
known_datacenters = 1
leader = true
leader_addr = 127.0.0.1:8300
server = true
raft:
applied_index = 181
[...]
state = Leader
term = 2
runtime:
arch = amd64
[...]
version = go1.9.2
serf_lan:
coordinate_resets = 0
encrypted = false
[...]
query_time = 1
serf_wan:
coordinate_resets = 0
encrypted = false
[...]
query_time = 1
Si tu te connectes sur http://127.0.0.1:8500/ui/ tu auras l’IHM de Consul :

On va maintenant jouer avec la base de donnée clé/valeur (KV) :
$ consul kv put dossier/
Success! Data written to: dossier/
$ consul kv put dossier/value 42
Success! Data written to: dossier/value
$ consul kv get dossier/value
42
Tu peux vérifier dans l’IHM à chaque étape. Voir si ça bouge. Ou même faire les manipulations dans l’UI.

Et maintenant un petit peu de ménage :
$ consul kv delete dossier/value
Success! Deleted key: dossier/value
$ consul kv delete dossier/
Success! Deleted key: dossier/
Voilà, nous avons déjà vu pas mal de choses avec Consul.
La screencast qui correspond est disponible sur Youtube.
N’hésitez pas à partager, poser des questions, faire des remarques.
À bientôt pour la suite.

Nicolas me demandait dans un autre billet :
Pas l’objet principal de ton article, mais concrètement à quoi ça sert Consul ? Quelle utilisation en as-tu ?
Sinon, une raison particulière à utiliser terraform-inventory plutôt que l’inventaire dynamique fournit par OpenStack ?
Comme répondre à tout cela ferait un commentaire très long, un article en plus, c’est encore mieux :)
Consul c’est quoi ? On va s’inspirer de Introduction to Consul :
Ça peut servir à plein d’autres choses :
Ensuite, pourquoi utiliser terraform-inventory ?
Voilà Nicolas. Est-ce que ça répond à tes questions ?