Κυριακή 27 Μαρτίου 2011

Δημιουργία chroot για development

Αν ο κώδικας που γράφει κάποιος δεν έχει ιδιαίτερες απαιτήσεις (π.χ., βιβλιοθήκες ή εργαλεία) ούτε reverse dependencies (π.χ., αν γράφουμε λύσεις για τα προβλήματα στο project euler) τότε το να γίνει η ανάπτυξη στο ίδιο περιβάλλον που γίνονται και οι υπόλοιπες δουλειές στον υπολογιστή (π.χ., browsing, συγγραφή κειμένων) δεν δημιουργεί κανένα πρόβλημα.

Αν όμως οι φιλοδοξίες μας είναι μεγαλύτερες (π.χ., αλλαγές στο window manager ή στο office suite ή ακόμα και σε κάτι πιο θεμελιώδες όπως ο kernel ή ο boot loader) τότε κάποια μορφή "απομόνωσης" του περιβάλλοντος ανάπτυξης είναι απαραίτητη, για 2 κυρίως λόγους.
  1. Σταθερότητα στο περιβάλλον ανάπτυξης. Το να "σκάει" ο κώδικάς μας λόγω trivial api/abi updates σε βιβλιοθήκες από δω κι από κει δεν είναι καθόλου διασκεδαστικό όταν η ανάπτυξη δεν έχει φτάσει ακόμα σε μια στοιχειωδώς "σταθερή κατάσταση".
  2. Σταθερότητα στο κυρίως μας σύστημα. Π.χ., το να χρειαστεί να κάνεις μια παρουσίαση με το presentation πρόγραμμα στο οποίο έκανες αλλαγές εχθές στις 3 τη νύχτα χωρίς να το δοκιμάσεις πρώτα, επίσης δεν είναι διασκεδαστικό, πόσο μάλλον αν οι αλλαγές αφορούν κάποιο πιο σημαντικό ακόμα κομμάτι του υπολογιστή (xorg, pulseaudio, dbus, init, udev ...)
Η πιο απλή τέτοια μορφή "απομόνωσης" είναι το chroot.

Το μόνο που μας προσφέρει ένα "chroot jail" στο linux είναι ένα διαφορετικό filesystem namespace από ότι στο κανονικό σύστημα. Π.χ., το "/srv/projects/devroot" θα φαίνεται ως"/" κλπ. Όλα τα άλλα παραμένουν κοινά με το host, π.χ., το process/pid space είναι κοινό και το network namespace (interfaces, ports κλπ) είναι επίσης κοινό.

Το chroot μας βολεύει ιδιαίτερα όταν το περιβάλλον εντός του chroot (εκδόσεις βιβλιοθηκών / ABI) είναι ίδιο με του host. Αυτό που μας προσφέρει σε αυτή την περίπτωση είναι ότι εντός του chroot μπορούμε να κάνουμε "apt-get build-dep libreoffice" και να αφήσουμε το apt να κατεβάσει του κόσμου τα -dev packages και όταν τελειώσουμε την ανάπτυξη απλά να σβήσουμε το chroot όπως είναι, διατηρώντας έτσι το σύστημα καθαρό και χωρίς να χρειάζεται να κρατάμε λογαριασμό για το τι αλλαγές κάναμε ώστε να τις αναιρέσουμε όταν τελειώσει η ανάπτυξη / αποσφαλμάτωση (που μπορεί να κρατήσει και μέρες πολλές φορές).

Επίσης, το chroot με ίδιο περιβάλλον είναι ο ευκολότερος τρόπος που μας δίνει πλήρη πρόσβαση στις υπηρεσίες του host, π.χ., accelerated graphics (χρήσιμο για προγραμματισμό παιχνιδιών π.χ., ή effects), ήχο μέσω του pulseaudio του host, dbus, άμεση πρόσβαση στο hardware του host κλπ κλπ.

Δημιουργία "βασικού" chroot

Ας δούμε λοιπόν πώς μπορούμε να φτιάξουμε ένα chroot κατάλληλο για ανάπτυξη. Το πρώτο που χρειαζόμαστε είναι μία βασική εγκατάσταση μιας διανομής στο directory που διαλέξαμε για chroot. Εδώ υπάρχουν διάφορες επιλογές:
  1. Πολλές διανομές δίνουν εργαλεία για την κατασκευή μιας τέτοιας θεμελιώδους εγκατάστασης, π.χ., debootstrap για debian/ubuntu και febootstrap για fedora.
  2. Μπορούμε να πάρουμε ένα έτοιμο tarball, π.χ., ένα stage3 tarball για το gentoo (όπως αυτό) ή ένα από τα openvz templates που βρίσκονται εδώ (για τις περισσότερες διανομές). Η προσωπική μου προτίμηση είναι τα tarballs που χρησιμοποιεί το launchpad για τους autobuilders τα οποία μπορείτε να βρείτε στη διεύθυνση https://edge.launchpad.net/api/devel/ubuntu///chroot_url. Π.χ., για amd64 maverick https://edge.launchpad.net/api/devel/ubuntu/maverick/amd64/chroot_url.
Ας προετοιμάσουμε λοιπόν τώρα το chroot
sudo tar -xjvf chroot-ubuntu-*.tar.bz2
mv chroot-autobuild devroot
Το επόμενο βήμα μας είναι να φροντίσουμε ώστε να λειτουργεί το apt στο chroot μας:
sudo cp /etc/hosts           devroot/etc/
sudo cp /etc/resolv.conf devroot/etc/
sudo cp /etc/apt/sources.list devroot/etc/
Αν έχουμε εγκατεστημένο caching debian proxy (π.χ., aptcacher-ng) καλό είναι να αντιγράψουμε και τα δικά του settings (π.χ., sudo cp /etc/apt/apt.conf.d/98aptcacher devroot/etc/apt/apt.conf.d).

Για να δουλεύουν τα πράγματα εντός του chroot σωστά, καλή ιδέα είναι να προσφέρουμε στα προγράμματα που τρέχουν εντός του chroot πρόσβαση στις βασικές υπηρεσίες (virtual filesystems) του host. Αυτό γίνεται μέσω του --bind option στην εντολή mount:
for fs in proc sys dev "dev/pts" "sys/kernel/debug"; do
if [ -d "/${fs}" ]; then
sudo mount --bind "/${fs}" "devroot/${fs}"
fi
done


Πρόσβαση στον X server του host

Ανάλογα με το τι θέλουμε να κάνουμε στο chroot και το τι επίπεδο "απομόνωσης" χρειαζόμαστε, υπάρχουν διάφορες επιλογές για την επικοινωνία με τον X server του host:

  1. Μέσω του ίδιου unix domain socket που χρησιμοποιούν και οι εφαρμογές του host
  2. Μέσω TCP
  3. Μέσω ssh X11 forwarding
  4. Με χρήση άλλου X server ο οποίος κάνει render σε ένα παράθυρο στο host X server. (Προτείνεται ο Xephyr).
  5. Μέσω VNC ή FreeNX server στο chroot στο οποίο συνδέουμε ένα client από το host.
Σε αυτό το post θα περιγράψω την απλούστερη επιλογή (την 1) η οποία έχει το πλεονέκτημα ότι δουλεύει εύκολα στις σύγχρονες διανομές (που έχουν συνήθως απενεργοποιήσει το TCP για λόγους ασφάλειας) καθώς και μας παρέχει τα περισσότερα features (π.χ., accelerated Xrender) με τη λιγότερη "βαβούρα". Περισσότερα για τις υπόλοιπες μεθόδους σε επόμενα post.

Για την επικοινωνία με το host X server μέσω του unix domain socket χρειαζόμαστε 2 πράγματα:
  1. Πρόσβαση στο socket (το οποίο είναι ένα αρχείο)
  2. Πιστοποίηση
Για το 1. το μόνο που χρειάζεται να ξέρουμε είναι ότι το αρχείο του socket είναι το /tmp/.X11-unix/X0 στις περισσότερες περιπτώσεις. Άρα αρκεί να κάνουμε
mount --bind /tmp devroot/tmp  
και πλέον τα αρχεία κάτω από το /tmp είναι προσβάσιμα εντός του chroot.

Για το 2. υπάρχουν διάφορες επιλογές, ανάλογα με το τι θέλουμε. Αν θέλουμε να τρέξουμε κάτι ως root μέσα στο chroot, μπορούμε να δώσουμε την κατάλληλη "άδεια" στο root χρήστη να επικοινωνεί με τον X server μας. Αυτό γίνεται τρέχοντας από το host:
xhost SI:localuser:root
(Προσέξτε το localuser, σημαίνει πρόσβαση μέσω unix domain socket).

Μια άλλη λύση είναι να αντιγράψουμε στο chroot τα /etc/passwd , /etc/shadow και /etc/shadow- και να κάνουμε και mount --bind το /home στο devroot/home. Σε αυτή την περίπτωση τρέχουμε τα προγράμματα ως ο ίδιος χρήστης που υπάρχει και στο host οπότε δε χρειάζεται επιπλέον πιστοποίηση.

Όταν κάνουμε compiles είναι καλύτερο να τρέχουμε ως απλός χρήστης, γιατί αλλιώς μπορεί να παρατηρήσουμε ότι το compile έκανε ζημιά στο σύστημα (π.χ., υπήρξε γνωστό περιστατικό που το compile του kernel ως root "χαλούσε" το /dev/null ...)

Καμιά φορά όμως που απλά θέλουμε να δοκιμάσουμε κάτι, βολεύει το να κάνουμε mount --bind όσο το δυνατόν λιγότερα πράγματα για να προστατευθούμε από το ενδεχόμενο να σβήσουμε π.χ., το $ΗΟΜΕ κατά λάθος όταν θα σβήσουμε το chroot αργότερα ...


D-BUS

Πολλά προγράμματα (κυρίως GUI και desktop services) στις μέρες μας χρειάζονται πρόσβαση στο D-BUS για να δουλέψουν (σωστά). Για να παρέχουμε πρόσβαση στο D-BUS του host αρκεί να κάνουμε (ως root):
mkdir devroot/var/lib/dbus
mount --bind {,devroot}/var/lib/dbus

Pulseaudio

Παρόμοια, για να έχουμε ήχο από τα προγράμματα του chroot, χρειάζεται να έχουμε πρόσβαση στον Pulse audio sound server του host, ο οποίος πλέον χρησιμοποιείται πρακτικά από όλα τα προγράμματα στα σύγχρονα desktop.

Για να το πετύχουμε αυτό χρειαζόμαστε πρόσβαση στο /var/run αφού εκεί έχει ο pulse audio server το (unix domain) socket του:
mount --bind {,devroot}/var/run  

Μπαίνοντας στο chroot

Μετά από όλη αυτή την προετοιμασία, είμαστε επιτέλους έτοιμοι να "μπούμε" στο chroot:
sudo chroot devroot /usr/bin/env -i HOME=/root TERM=$TERM DISPLAY=$DISPLAY \
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin \
/bin/bash --login
και εντός του chroot πλέον μπορούμε να κάνουμε apt-get update && apt-get install οτιδήποτε.

Επίσης μπορούμε να τρέξουμε μια εφαρμογή και να παρατηρήσουμε ότι έχει κανονικά(accelerated) γραφικά και ήχο. Αν θέλουμε να δουλέψουμε ως χρήστης και έχουμε κάνει τις απαραίτητες ρυθμίσεις που περιγράφονται παραπάνω, μπορούμε να κάνουμε su - myuser.


Καθαριότητα

Μόλις τελειώσουμε τη δουλειά μας στο chroot είναι σημαντικό να "καθαρίσουμε" τα bind mounts όσο το δυνατόν πιο γρήγορα, για να μη σβήσουμε κάτι σημαντικό κατά λάθος.

Αν δεν είστε σίγουροι για το ποια ακριβώς bind mounts έχετε κάνει, η "brute force" λύση είναι
κάτι σαν και αυτό:
for fs in $( mount | grep devroot | sed 's/.* on //' | sed 's/ type.*//' ); do
sudo fuser -k "${fs}"
sudo umount "${fs}"
done
Το τρέχετε αυτό 3-4 φορές (ώστε να μη μας νοιάζει η σειρά που γίνονται τα unmounts) και όταν το "grep devroot /proc/mounts" δε δείχνει πια τίποτα έχουμε καθαρίσει :P


Scripting / Αυτοματισμός

Επειδή φυσικά τα παραπάνω είναι υπερβολική φασαρία για να τα κάνει κανείς "με το χέρι" κάθε φορά που θέλει να στήσει ένα καινούργιο chroot, σχεδόν όλοι όσοι ασχολούνται έχουν φτιάξει τα δικά τους script για την αυτοματοποίηση της διαδικασίας.

Παραδείγματα είναι το schroot και το dchroot που θα βρείτε στο debian/ubuntu, το openroot και άλλα. Γενικά αυτά τα scripts είναι συνήθως ή του τύπου "mychroot [myapp]" όπου το script κάνει την προετοιμασία, τρέχει το πρόγραμμα και μετά "καθαρίζει" αυτόματα, ή του τύπου mychroot_setup και mychroot_cleanup. Ο δεύτερος τρόπος βολεύει όταν κάποιος "μπαινοβγαίνει" συχνά και δε θέλει να επαναλαμβάνει το setup / cleanup όλη την ώρα.

Σε κάθε περίπτωση όμως είναι καλό να ξέρουμε τι ακριβώς συμβαίνει όταν τρέχουμε αυτά τα script για να μη βρεθούμε προ εκπλήξεως αργότερα (αγάπη μου έσβησα το $HOME και τώρα θα είμαστε $HOME-less κλπ κλπ :P) καθώς και για να μπορούμε να καταλάβουμε και να διορθώσουμε τυχόν σφάλματα και αυτή είναι η ταπεινή συνεισφορά αυτού του άρθρου ...

Happy (and safe) Hacking!

Παντελής

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου