Laptop ? Lab top !

Pierre Gambarotto

Created: 2019-03-20 mer. 14:15

Qui suis-je ?

  • responsable info de l'IM Toulouse depuis peu
  • ASR linux
  • formateur développement d'appli web
  • pas très doué en réseau

Motivations

  • infra de l'IMT à moderniser
  • mes compétences à moderniser :-)
    • ifconfig/netstat/route/brctl -> ip/ss/bridge
    • iptables -> nftables linux > 4.17
    • systemd
  • accueil de stagiaires

=> maquettes à réaliser

Cahier des charges

  • Interface simple:
demo up
# work work work
demo down
  • doit supporter maison <–> métro <–> boulot sans redémarrage
  • ne doit pas gêner les usages courants
  • ne doit pas laisser de traces après un reboot

Container/VM

  • sécurité: séparer ce qui peut être fait en root du reste
  • isolation

Ce que l'on va voir

couches basses vers couches hautes:

  • réseau
  • virtualisation
  • automatisation de configuration

Pour chaque: mes choix, et les alternatives survolées

Réseau

  • dummyint
  • bridge
  • dnsmasq

Réseau privé séparé

  • isolation: réseau privé NATé
  • maquette d'infra: plusieurs machines => bridge

bridge linux ~ switch

Interface Dummy ?

  • bridge: périphérique ethernet
    • source de certains traffics: STP
    • besoin d'une @MAC
    • @MAC initiale, puis prend celle de la première interface ajoutée
  • dummy: même principe que loopback, mais au niveau 2

Pour un bridge avec @MAC fixe

  1. créer interface dummy
  2. créer bridge,
  3. «brancher» l'interface dummy sur le bridge
generate_mac(){
  hexdump -vn3 -e '/3 "52:54:00"' -e '/1 ":%02x"' -e '"\n"'\
   /dev/urandom
}
show_mac_address(){
  ip -j link show $1 | jq '.[].address'
}
ip link add dummytest address `generate_mac` type dummy
show_mac_address dummytest # "52:54:00:aa:68:d5"
ip link add brtest type bridge
show_mac_address brtest # "46:d9:2d:f0:68:9d"
ip link set dev dummytest master brtest
show_mac_address brtest # "52:54:00:aa:68:d5"

Niveau IP

  • un réseau privé
  • une @IP pour le bridge
  • une @IP pour chaque machine branchée sur le bridge
  • NAT
  • dhcp dynamique ou statique sur le réseau privé
  • dns sur un domaine fictif, non routé

Iptables

  • règles classiques pour du NAT

Problèmes spécifiques à iptables:

  • politique par défaut: ACCEPT ou REJECT ?
  • interférences …
  • période de turbulences: passage iptables -> nftables, mais support de l'ancienne syntaxe

Politique choisie

  • ACCEPT
  • j'écrase tout, mais sauvegarde/restoration possibles

=> iptables-save/iptables-restore

LA partie à accuser quand rien ne marche …

dhcp dnsmasq

  • allocation dynamique par défaut
  • allocation statique par lecture de fichier, --dhcp-hostfile.

Si un répertoire, prend en compte tous les fichiers.

  • relecture des fichiers après un kill -HUP

Format:

52:54:00:f8:2e:b3,192.168.100.200,test.dev
#@MAC,@IP,dns

=> s'automatise très bien

nvbr

Automatisation de tout ce qui précède:

projet sur plmlab

Objectifs:

  • avec les choix précédents, automatiser la configuration
  • ne rien rendre pérenne, un reboot doit pouvoir sauver la situation

Démarrage

sudo DEBUG=true nvbr up
debug: set vbr0 up, state in /var/run/nvbr/vbr0
debug: bridge=vbr0
debug: mac=52:54:00:f0:51:09
debug: dummyint=vbr0dummy
debug: ipbr=192.168.100.1/24
debug: dnsdomain=.dev
debug: generate dummy interface vbr0dummy
debug: create bridge vbr0
debug: bridge up
debug: activate routing
debug: generate firewall rules
debug: oldrules stored in /var/run/nvbr/vbr0/iptables_before_nvbr
debug: new rules stored in /var/run/nvbr/vbr0/natrules
debug: start dnsmasq

# work  work work

Arrêt

# work work work
sudo DEBUG=1 nvbr down
debug: stop dnsmasq
debug: restore firewall state from /var/run/nvbr/vbr0/iptables_before_nvbr
debug: put down bridge up vbr0
debug: bridge=vbr0
debug: dummyint=vbr0dummy
debug: destroy /var/run/nvbr/vbr0

/var/run/nvbr/${bridge_name}

/var/run/nvbr/vbr0/
├── dnsmasq.conf         #configuration dnsmasq
├── dnsmasq_leases       # baux dhcp
├── dnsmasq.pid
├── hosts.d              # dhcp statique
├── iptables_before_nvbr # sauvegarde des règles
└── natrules             # règles appliquées

Gestion des configurations stattiques

# nvbr addhost/delhost bridge_name machine_name @mac,@ip,dnsname
sudo nvbr addhost vbr0 node1 52:54:00:f8:2e:b3,192.168.100.200,server.dev

crée dans hosts.d/node1:

52:54:00:f8:2e:b3,192.168.100.200,server.dev

et fait prendre en compte par dnsmasq la nouvelle config.

Configuration

  • uniquement par variable d'environnement pour le moment
netbase=192.168.123
sudo DEBUG=1 ipbr=${netbase}.1/24 \
  dhcp_ipmin=${netbase}.2 dhcp_ipmax=${netbase}.100 /
  nvbr up

Partie Réseau: le bilan

  • très utile pour remettre mes compétences à jour
  • nvbr: permet d'avoir un exemple de configuration complet sous la main

À creuser:

  • bridge + tap -> macvtap
  • fixer l'@MAC du bridge et virer la dummy int.
  • nvbr: gestion en // de plusieurs bridges

qemu/kvm

  • solution de virtualisation complète et performante

=> possibilités de passthrough (GPU, token usb …)

  • bas niveau: CLI à rallonge, nombreuses options.

Partie réseau: une interface réseau d'une VM correspond à un périphérique tap ou macvtap

CLI, la base

qemu-system-x86_64 # commande de base
kvm # raccourci pour -enable-kvm
-m 256 # fixe la mémoire de base disponible
-smp 2 # 2 CPU
disk.qcow2 # un fichier d'image disque 

Interfaces tap

interface tap: permet à un programme de parler à un périphérique réseau géré par le noyau

# création de l'interface tap
sudo ip tuntap add mod tap dev tap0
sudo ip link set dev tap0 up
# ajout au bridge
sudo ip link set dev tap0 master vbr0

# démarrer la VM
kvm -netdev tap,br=vbr0,ifname=tap0,script=no,downscript=no,id=n1 \
    -device virtio-net-pci,netdev=n1 disk.img

# qemu 3.1+: nic fusionne netdev et device
kvm -nic tap,br=vrb0,ifname=tap0,script=no,downscript=no disk.img

v1: script=/etc/qemu-ifup downscript=/etc/qemu-ifdown => automatise l'ajout/la suppression de l'interface tap au bridge

# création de l'interface tap
sudo ip tuntap add mod tap dev tap0
sudo ip link set dev tap0 up
# ajout au bridge: fait par /etc/qemu-ifup

# démarrer la VM
kvm -nic tap,br=vrb0,ifname=tap0 disk.img

v2: automatisation de la création de l'interface tap

kvm -nic bridge,br=vbr0,model=virtio-net-pci disk_img.qcow2
# /usr/lib/qemu/qemu-bridge-helper crée une interface tap
# et l'ajoute au bridge spécifié

Le bridge visé doit apparaître dans /etc/qemu/bridge.conf:

allow vbr0

Pour quelques sudo de moins

  • kvm : groupe kvm pour utiliser /dev/kvm
  • qemu-bridge-helper : chmod +s

qemu/kvm: bilan

  • très performant
  • documentation pas forcément à jour
  • trop bas niveau dans le contexte

Libvirt

  • interface pour hyperviseur
  • entre autre: qemu/kvm, lxc
  • gui: virt-manager
  • cli: virsh
  • orienté API : représentation xml des objets gérés: réseau, machine, stockage

Connexion au démon libvirtd

  • session utilisateur ou session système

Pour facilement réutiliser le travail fait sur nvbr, on utilise la session système.

  • url de connexion: qemu+ssh://root@localhost/system
  • authentification par clef ssh, permet de gérer sans mot de passe.

Intérêt pour construire des maquettes:

  • facile de répliquer et d'intégrer le travail précédent
  • possible de déployer sur un libvirt distant
  • environnement bien documenté
  • facile à scripter: on définit par la GUI, on exporte le XML correspondant par la CLI

Définition d'un réseau libvirt

GUI possible également.

export LIBVIRT_DEFAULT_URI=qemu+ssh://root@localhost/system
cat > /tmp/vbr0.xml <<EOF
<network>
    <name>nvbr_vbr0</name>
    <forward mode="bridge"/>
    <bridge name="vbr0"/>
</network>
EOF
virsh net-define /tmp/vbr0.xml
virsh net-list --all
# Name              State      Autostart   Persistent
# nvbr_vbr0         inactive   no          yes
virsh net-start nvbr_vbr0; virsh net-undefine nvbr_vbr0
virsh net-list --all
# Name              State    Autostart   Persistent
# nvbr_vbr0         active   no          no

Boot d'une VM

Création initiale à partir d'une image d'installation sur le réseau:

virt-install \
--name debian9 \
--ram 1024 \
--disk path=./debian9.qcow2,size=8 \
--vcpus 1 \
--os-type linux \
--os-variant debian9 \
--network network=nvbr_vbr0 \
--console pty,target_type=serial \
--location 'http://ftp.fr.debian.org/debian/dists/stretch/main/installer-amd64/' \

Libvirt: bilan

  • surcouche pas trop intrusive à qemu/kvm
  • récupération d'une configuration réseau existante aisée
  • facile à automatiser:
    • GUI -> virsh dumpxml -> virsh define maversion.xml
    • hooks
  • manipulation de socles distants possibles

Génération d'image disque

  • ne pas refaire le processus d'installation
  • en pratique: manipulation d'image disque

À bas niveau

  • au niveau fichier: lvm
  • au niveau image: qemu-img
qemu-img create -f qcow2 -b image_de_base.qcow2 overlay.qcow2

On démarre ensuite une VM avec overlay.qcow2 comme disque.

  • écriture sur overlay.qcow2
  • lecture d'abord sur overlay.qcow2, puis sur image_de_base.qcow2

Libvirt

virsh snapshot-create domain snapshot.xml
virsh snapsho-revert domain snapshot.xml
  • toute la définition de la machine, ou les disques seulement
  • --live : sauvegarde la mémoire également

virt-builder

Les solutions bas-niveau m'imposent toujours d'installer une image de base.

  • ok pour les systèmes que je gère: point de vue ASR
  • rédhibitoire quand je veux valider un projet sur 5 distributions: point de vue dév

libguestfs héberge des images disques minimales, correspondant à l'installation initiale.

  • virt-builder permet de générer des images bootables à partir des images du projet libguestfs
  • facilement personnalisable: script avant boot, script au boot …
  • gère un cache des images téléchargées
sudo apt install libguestfs-tools
virt-builder debian-9 --format qcow2 --timezone "Europe/Paris" \
  --ssh-inject root:file:$HOME/.ssh/id_ed25519.pub \
  --install console-setup,keyboard-configuration,ifupdown,isc-dhcp-client,ssh \
  --edit '/boot/grub/grub.cfg: s/\/dev\/vda1/\/dev\/sda1/g' \
  --edit '/etc/network/interfaces: s/ens2/ens3/g' \
  --hostname vmtest \
  --root-password password:toto

=> bonne solution pour de l'intégration continu d'un produit à tester sur plusieurs distributions linux

Vagrant

solution intégrée:

  • box: gestion d'image
  • provider: interface avec des gestionnaires de VM/Conteneur
    • VirtualBox, Vmware, Libvirt
    • docker, lxc
  • provisionner: personnalisation de l'image de base
  • un fichier unique permet de spécifier un environnement de maquettage => Vagrantfile

Vagrantfile

  • base ruby
  • DSL Vagrant
  • permet de gérer et configurer les concepts suivants:
    • box: image de base
    • provider: image de base + config -> VM sur un hyperviseur
    • provisionner: personnalisation de la VM
Vagrant.configure("2") do |config|
  config.vm.box = "debian/stretch64"
  config.vm.provider :libvirt do |libvirt|
    libvirt.default_prefix = "lab"
    libvirt.host = "localhost"
    libvirt.username = "root"
    libvirt.id_ssh_key_file = "id_ed25519"
    libvirt.connect_via_ssh = true
  end
  config.vm.hostname = "citron"

  config.vm.network "public_network", 
                    dev: "vbr0",
                    type: "bridge",
                    mode: "bridge"
end

Cycle de vie

premier vagrant up -> download or update local box -> ask provider to run vm -> connect to running vm through management network -> run provisionner

La phase de provisionning n'est pas rejouée automatiquement ensuite.

vagrant up
vagrant suspend
vagrant resume # up can be used
vagrant destroy

Réseau de management

  • réseau privé automatique, 192.168.121.0/24 par défaut.
  • connexion ssh avec le compte vagrant
  • gestion automatique de la configuration
vagrant ssh # ssh with config given by

vagrant ssh-config
# Host default
#   HostName 192.168.121.13
#   User vagrant
#   Port 22
#   UserKnownHostsFile /dev/null
#   StrictHostKeyChecking no
#   PasswordAuthentication no
#   IdentityFile .../.vagrant/machines/default/libvirt/private_key
#   IdentitiesOnly yes
#   LogLevel FATAL
#   ProxyCommand ssh 'localhost' -l 'root' -i \
#     '~/.ssh/id_ed25519' nc -q0 %h %p

Partage de dossier

  • le répertoire contenant le Vagrantfile est vu dans les VM sous `/vagrant`
  • nfs/plan9 ou rsync.
  • entièrement configurable
config.vm.synced_folder ".", "/vagrant", 
  type: "nfs", nfs_version: 4, nfs_udp: false
config.vm.synced_folder ".", "/vagrant", 
  type: "rsync", rsync_exclude: ".git/"

multi machines

  nodes = [
    {
      name: "node1",
      mac: "52:54:00:f8:2e:b3",
      ip: "192.168.100.200",
      dnsname: "server.dev"
    },
    {
      name: "node2",
      mac: "52:54:00:13:6d:6a",
      ip: "192.168.100.150",
      dnsname: "client.dev"
    }
  ]
  nodes.each do |node|
    config.vm.define node[:name] do |n|
      n.vm.hostname = node[:dnsname]
      n.vm.network "public_network",
                   dev: "vbr0",
                   type: "bridge",
                   mac: node[:mac],
                   mode: "bridge"
    end
  end

Provisionning

  • shell: copie un script sur la VM puis l'exécute.
  • puppet/ansible/chef
config.vm.provision "install puppet agent", type: "shell",
                     path: "script/install_puppet_6_agent.sh"

config.vm.provision "test", type: "puppet",
                    manifests_path: ["vm", "/vagrant"],
                    manifest_file: "manifests/limesurvey.pp",
                    options: "--modulepath=/vagrant/modules",
                    environment_variables: { "LANG": "C" }

Personnalisation et intégration

triggers: insérer des actions dans le cycle de vie.

# type: need last version of vagrant, 
#with VAGRANT_EXPERIMENTAL="typed_triggers"
# run after all vms have been destroy
config.trigger.after :destroy, type: :command do |t|
  t.info = "remove vagrant-libvirt network"
  t.run = {inline: "./script/del_vagrant-libvirt_net.sh"}
end
config.trigger.before :up do |t|
  t.info = "adds dnsmasq static config"

  t.ruby do |env,machine|
    net = machine.config.vm.networks.select{|n| n[0]==:public_network and n[1][:mac] }[0][1]
    node = nodes.select{ |n| n[:mac] == net[:mac] }.first
    nvbr=`which nvbr`.chomp
    system "sudo #{nvbr} addhost '#{net[:dev]}' '#{node[:name]}' '#{node[:mac]},#{node[:ip]},#{node[:dnsname]}'"
  end
end

# run after each vm destruction
config.trigger.before :destroy do |t|
  t.info = "deletes dnsmasq static config"
  t.ruby do |env,machine|
    net = machine.config.vm.networks.select{|n| n[0]==:public_network and n[1][:mac] }[0][1]
    node = nodes.select{ |n| n[:mac] == net[:mac] }.first
    nvbr=`which nvbr`.chomp
    system "sudo #{nvbr} delhost '#{net[:dev]}' '#{node[:name]}'"
  end
end

Bilan Vagrant

  • excellente interface utilisateur
  • facile d'intégrer des outils
  • bug trouvés dans vagrant-libvirt :-(

Bilan et perspectives

  • j'ai beaucoup (ré)appris.
  • utilisation actuelle:
    • test cluster de fichiers: sheepdog/ceph
    • validation de recette puppet en clients/serveur
    • très récent: openshift ^_^
  • plmlab/gamba/natbridge: utilisable, manque doc et intégration libvirt
  • plmlab/gamba/vagrantfiles: partage de conf Vagrant basique: à faire !