Votre navigateur est obsolète !

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

×

Nicolas Ledez

Architecte DevOps

Nicolas Ledez
47 ans
Permis de conduire
Rennes France
Situation professionnelle
En poste
Ouvert aux opportunités
Présentation
De formation industrielle et autodidacte en informatique. Je travaille depuis 15 ans dans le secteur de l'IT.

J'ai travaillé de manière hétérogène, tant en terme de clients que de technologie.

J'ai donc aujourd'hui un domaine de compétence assez vaste. Ce qui me permet d'appréhender rapidement des projets complexes et fluidifie le dialogue avec mes différents interlocuteurs.
CV réalisé sur DoYouBuzz
Live from my brain with some lag blog.ledez.net
OVH OpenStack Stein port_security avec Terraform
24 mars 2022

Terraform avec OpenStack Stein

In English

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 :

  • La liste des réseaux auxquels se rattacher
  • La liste des security groups des lesquels mettre la VM

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à ! :)

OVH OpenStack Stein port_security with Terraform
23 mars 2022

Terraform avec OpenStack Stein

En français

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:

  • The list of networks to attach to
  • The list of security groups to put the VM in

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! :)

Sauvegardes, analyse de risque, PRA et PCA
22 mars 2021

Sauvegardes, analyse de risque

Bon, je ne vais pas revenir sur l’incident d’OVH :

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/…).

Cas numéro 1 - un serveur dédié chez OVH

L’analyse de risque

Légende :

  • Mon app est plus ou moins injoignable -> +/-
  • Mon app est totalement injoignable -> ☠️
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.

Les contre-mesures

Je fais un rm -rf dans mes fichiers

Là, 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 :

  • Passer sur plusieurs machines
  • Passer sur des vm “cloud” dont le disque est “volatile”
  • Plusieurs To de fichiers à sauvegarder
  • Plusieurs millions de fichiers à sauvegarder
  • Temps de restauration quand on a plusieurs To

Sinon, ce que je mets souvent en place, c’est :

  • rsync sur un serveur de sauvegarde distant (on va y revenir plus tard)
  • des snapshoots sur mon serveur de sauvegarde (j’utilise ZFS maintenant)
  • des snapshoots sur x jours / x semaines / x mois

Si des fichiers sont supprimés, je fais un rsync dans l’autre sens. À partir d’un snapshoot au besoin.

Je fais un DELETE from ITEMS dans ma base de données

Dans ce cas, je fais tous les jours des sauvegardes locales soit :

  • mysqldump/pg_dump
  • quand la base est trop grosse à exporter, backup à chaud/froid style percona backup. Une complète par semaine, puis une incrémentale par jour

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.

Je me fais pirater (remplacement des fichiers)

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.

Un composant de la machine tombe en panne

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…

Le disque dur tombe en panne

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…

Ma machine ne redémarre pas

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.

La machine brûle / l’hébergeur devient injoignable

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 :

  • Réinstaller l’OS
  • Réinstaller les applicatifs
  • Réinstaller les données

Et comme votre serveur n’est plus là, aucune chance de savoir comment c’était configuré avant…

Donc :

  • Gestionnaire de configuration => Saltstack/Ansible/Chef/Script shell/au pire de la doc
  • Passez régulièrement votre gestionnaire de configuration
  • Rien à la main, ou alors documenté, si possible au même endroit que le gestionnaire de configuration
  • Bien sûr, sauvegardez ça aussi…
  • Faire des snapshoot des VM, ça permet de relancer le service plus vite sur un autre site, voir un autre hébergeur si l’hyperviseur est installable ailleurs
  • Et on teste de temps en temps
  • Mais surtout à chaque changement majeur ! (version d’OS / d’hyperviseur)

Le continent où est hébergé le serveur disparaît

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….

Pour résumer

Toutes les semaines :

  • Snapshoot des vm
  • Backup full de ma BDD
  • Transfert de tout ça sur le serveur de sauvegarde distant

Tous les jours :

  • Backup de la BDD
  • Rsync des fichiers
  • Rsync des sauvegardes de BDD

Le serveur de sauvegarde

J’en parle, mais quoi et où ?

Pour réduire les risques :

  • Chez un autre fournisseur
  • Un minimum de kilomètres entre les DC (on parle souvent de 300km), accident nucléaire, météorite, tremblement de terre …
  • C’est le serveur qui va chercher les sauvegardes (pull). Si le serveur de prod est compromis, il ne faut pas que l’attaquant puisse se connecter à ce serveur
  • Filtrez les IP qui peuvent se connecter
  • Et toute protection pour blinder ce serveur est bonne à mettre en place

Et ça suffit ?

Ç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.

Et pourquoi ne pas mettre tout ça chez moi ?

C’est sûr qu’avec la fibre optique vous avez de plus en plus la possibilité de vous héberger. Sauf que :

  • Rentrer dans un DC est de plus en plus dur
  • Un DC qui brûle, c’est plutôt rare
  • Une coupure de fibre, ça arrive. Un DC a plusieurs arrivées, et des SLA garanties par les opérateurs, les techniciens vont donc ressouder les fibres très vite

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 ?

Et maintenant ?

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

Générer des certificats wildcard avec Terraform
09 oct. 2019

Terraform + Let's Encrypt

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épendances
  • requirements.txt -> les dépendances Python
  • ovh-create-consumer-key.py -> un script pour simplifier la création du fichier ovh.conf et ovh.sh
  • terraform.tf -> fichier Terraform qui définit la configuration des providers Terraform
  • variables.tf -> fichier Terraform qui définit les variables que l’on va utiliser dans notre plan Terraform
  • terraform.tfvars -> fichier Terraform qui donne les valeurs que l’on va utiliser pour générer notre certificat TLS
  • lecertificate.tf -> le plan Terraform qui permet de générer le certificat TLS chez Let’s Encrypt

On 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 quit

Comme 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 :

Création de clée d'API OVH/1

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

Création de clée d'API OVH/2

$ vi ovh.conf
application_key = XXXX
application_secret = XXXX

On 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.

Création de clée d'API OVH/3

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

Création de clée d'API OVH/4

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 = XXX

On peut même vérifier que le consumer_key est toujours valide :

$ ./ovh-create-consumer-key.py
Welcome Nicolas nothing to do

On 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.tfvars
  • parent_domain -> le nom de domaine racine pour le certificat. Dans mon cas hashicorp4noobs.fr
  • subdomain -> le sous-domaine pour mon wildcard. Je vais mettre tools. Au final, mon certificat sera sous la forme *.tools.hashicorp4noobs.fr
  • le_email -> l’email auquel Let’s Encrypt va envoyer les messages quand le certificat va expirer

On 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.tf

Et 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.pem

Comme 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 :

  • Utiliser Terraform
  • Avec Let’s encrypt
  • Créer des fichiers en local

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.

Comment récupérer un 'rm -rf /var/lib/mysql/*'
16 sept. 2019

Mysql crash

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 :

  • Couper l’application
  • Mettre une règle iptable

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.

Mise en place de l’environnement de test

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 va démarrer la restauration :

# 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

Et maintenant, laissons opérer la magie

Le script en mode --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 export

Récupération des fichiers supprimés

sudo /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`

Extraction des données aux formats CSV pour plus de sécurité

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 employees

Et voilà !

Remettre les données en place

# 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.sql

Diverses instructions

  • Ne jamais arrêter Mysql, jamais, jamais, jamais, jamais, jamais, jamais
  • Ne pas essayer de mysqldump pendant que l’on utilise cet outil, jamais (encore une fois)
  • On identifie le process mysql avec la commande ps ax | grep [m]ysqld
  • On ne doit avoir qu’un seul process. Le noter pour plus tard
  • On va vérifier les fichiers que l’on va pouvoir récupérer sudo lsof -p $MYSQL_PID
  • On peut voir tous les fichiers ‘(deleted)’
  • Et tant que l’on a Mysql qui est lancé, on va pouvoir récupérer ces fichiers

Pourquoi ça peut fonctionner

Dé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 :

  • Faire des backup
  • Tester les restaurations
  • Noter le temps de ces opérations pour avoir conscience du temps que cela va prendre en cas de crash
  • Si vous faites des mysqldump, pensez à aussi faire des sauvegardes des .frm régulièrement
How to recover a 'rm -rf /var/lib/mysql/*'
16 sept. 2019

Mysql crash

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:

  • Shutdown applications can access to Mysql
  • Put an iptable rule

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.

Setting up the test environment

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’re going to start the restoration:

# 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

And now, let’s let the magic work.

The script in --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 export

Recovery of deleted files

sudo /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`

Extraction of data in CSV formats for greater security

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 employees

And voilà!

Put the data back in place

# 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.sql

Various instructions

  • Never stop Mysql, never, never, never, never, never, never, never, never, never
  • Do not try to mysqldump while using this tool, never (again)
  • We identify the mysql process with the command ps ax | grep[m]ysqld
  • We should only have one process. Write it down for later
  • We’ll check the files we can recover sudo lsof -p $MYSQL_PID
  • We can see all the files deleted
  • And as long as we have Mysql running, we’ll be able to get those files back

Why it can work

Already, 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:

  • Make backups
  • Testing restorations
  • Note the time of these operations to be aware of the time it will take in case of a crash
  • If you do mysqldump, also think about making regular backups of.frm files
Serveur iPXE ou comment booter sans USB/CD/whatever
19 mai 2019

iPXE

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 :

  • Il faut retrouver un média de libre
  • Il faut construire une image
  • Il faut graver/flasher l’image sur le média
  • Booter sur le device
  • Et quand ça ne fonctionne pas, repasser par la case construire l’image

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 :

  • Le BIOS démarre
  • Il passe par le firmware de la carte réseau (boot menu & NIC)
  • Le firmware PXE fait une requête DHCP qui lui donne les informations
    • IP
    • Serveur BOOTP (TFTP pour simplifier)
    • Le nom de l’image avec laquelle booter
  • PXE télécharge l’image en TFTP
  • PXE boot l’image

Ç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 :

  • PXE télécharge l’image iPXE en TFTP
  • PXE boot l’image iPXE
  • iPXE télécharge le reste de sa configuration en HTTP (ou autre protocole)

Il te faudra donc :

  • Un serveur DHCP (sur ton routeur/firewall OpenSense)
  • Un serveur TFTP (toujours ton serveur OpenSense)
  • Une image iPXE (sur le serveur TFTP donc OpenSense)
  • Un serveur HTTP (chez OVH ou ton serveur local)

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 ?

Installation du serveur iPXE

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 boot

Installation des sources pour l’installeur Ubuntu

cd /var/www/ipxe
./install-ubuntu.sh

Généreration du binaire iPXE

cd /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.h

C’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 options

Maintenant, 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.zbin

Et voilà, c’est terminé pour la partie iPXE.

Maintenant, tu dois configurer le DHCP / TFTP (sur l’OpenSense)

On va commencer par DNSMasq pour la partie TFTP :

Configuration de DNSMasq

Dans Service / Dnsmasq DNS / Settings :

  • Cocher “Enable” si ce n’est pas fait
  • “Listen port” -> “5353” ou autre chose, mais pas 53 qui rentrerait en conflit avec Unbound
  • “Network interface” -> “LAN”, j’ai mis ça pour éviter de répondre aux requêtes sur la partie WAN
  • “Advanced” voir ci-dessous
enable-tftp
tftp-root=/tftp

Clique sur “Save”. Et passes à Unbound :

Configuration du DHCP

Dans Service / DHCPv4 / LAN :

  • Dans la partie “TFTP server”
    • “Set TFTP hostname” -> “192.168.2.1”
    • “Set Bootfile” -> “/tftp/undionly.kpxe”

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.kpxe

Et voilà ce que ça donne :

Screencast de boot iPXE

Tu peux voir le boot PXE + iPXE avec Memtest86+ puis un début d’installation Ubuntu.

ANAVI Light Controller, Sonoff-Tasmota & Domoticz in a boat
17 févr. 2019

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:

Use in Domoticz

If you want the same, you can do it with this configuration:

Open “Configuration” / “Configure module” it must look like this:

Configure module

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

MQTT Gateway in Domoticz

Now click on “Create Virtual Sensors”

Create Virtual Sensors

Now available in devices view:

Create Virtual Sensors

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

Configure Domoticz

Put the “Idx” from Domoticz into “Idx 1”.

Now you can change state and color in Domoticz:

Use in Domoticz

And voilà!

Consul 01 - en local
07 févr. 2018

PCi, Terraform, Ansible, Consul & Co.

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 :

  • Agent
  • Serveur

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 :

Consul UI liste des services Consul UI liste des noeuds Consul UI liste des clés/valeurs

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.

Consul UI avec des clés qui ont des valeurs

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.

Ca sert à quoi Consul ?
29 janv. 2018

PCi, Terraform, Ansible, Consul & Co.

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 :

  • Multicentre de données (datacenter). C’est un outil simplifier la vie des administrateurs système. C’est prévu de base pour fonctionner sur plusieurs datacenters. Les données se répliquent, et en toute logique c’est en cluster. Et ça fonctionne super bien.
  • Une base de donnée clé/valeur. Dans Consul tout est stocké dans la base de données. Chaque valeur y est stockée dans une clé. Il y a une hiérarchie avec des dossiers et tout. Et accessible via une API REST.
  • Service de découverte. Dans ces valeurs, on peut y trouver des inventaires de services. Dans l’exemple que je prendrais plus tard, je veux mettre X serveurs d’application Python. Pour configurer mon Haproxy, je vais aller chercher la liste des serveurs dans Consul.
  • Test de vie. Et justement, pour avoir la liste de mes serveurs d’application Python, je vais installer un agent Consul sur chaque machine et lui ajouter un test de vie. Dés que le serveur est prêt, il en informe consul qui va mettre à jours la configuration haproxy automagiquement. Et pareil quand le serveur sera arrêté.

Ça peut servir à plein d’autres choses :

  • Stocker les configurations de services
  • Stocker les secrets (avec Vault)
  • Monitoring (serveur OK/KO toussa)

Ensuite, pourquoi utiliser terraform-inventory ?

  • Ça fonctionne automatiquement avec le tfstate
  • Quel que soit le provider (Openstack, autre chose)
  • Je peux avoir autre chose dans mon projet Openstack et je ne veux pas y toucher :p

Voilà Nicolas. Est-ce que ça répond à tes questions ?