Το προφίλ μας στο Google Plus
0

Αυτόματη δημιουργία μηχανών με το Packer

Χρειαζόσαστε μία ή περισσότερες προσαρμοσμένες εικονικές μηχανές για development, για ένα CI/CD pipeline ή απλά για δοκιμές και πειραματισμούς; Πρέπει μήπως να φιλοξενείτε τις μηχανές σε περισσότερους από έναν υπολογιστές ή cloud providers; Ψάχνετε έναν τρόπο ώστε εσείς και όλοι οι συνεργάτες σας να δουλεύετε στο ίδιο ακριβώς περιβάλλον;

Ένα άριστο εργαλείο για την αντιμετώπιση αυτών και παρόμοιων ζητημάτων είναι το Packer από την HashiCorp, το οποίο αποτελεί Ανοικτό Λογισμικό και διατίθεται για όλα τα δημοφιλή λειτουργικά συστήματα. Αποστολή του Packer είναι να κατασκευάζει πανομοιότυπες εικονικές μηχανές –στο εξής machine images— για όλες τις (όχι-και-τόσο) γνωστές πλατφόρμες virtualization και cloud. Πιο συγκεκριμένα, ξεκινώντας από μία και μόνο κοινή προδιαγραφή (single source configuration), το Packer παράγει machine images για VirtualBox, VMware, QEMU, Amazon EC2, DigitalOcean, OpenStack, Google Cloud — και φυσικά για την πλατφόρμα του YouNameIt(TM). Διαβάστε σε αυτή την ιστοσελίδα για μερικά από τα σενάρια χρήσης του Packer.

Στο παρόν άρθρο επιδεικνύουμε πώς δουλεύουμε με το Packer για την αυτοματοποιημένη παραγωγή πλήρως ενημερωμένων machine images με το openSUSE Leap 42.3. Αναλόγως της επιλογής μας, μια τέτοια εικονική μηχανή θα φιλοξενείται σε περιβάλλον VirtualBox (πάνω από Linux, Mac OS X ή Windows host), σε περιβάλλον VMware Fusion (πάνω από Mac OS X host) ή σε περιβάλλον QEMU (πάνω από Linux host). Πριν ξεκινήσουμε με το Packer, θα συζητήσουμε εκτενώς και για τα αρχεία JSON. Έτσι, θα είμαστε σε θέση να βγάζουμε γρήγορα νόημα από τα templates του Packer. Αλλά ας μην προτρέχουμε.

Λήψη και εγκατάσταση
Το Packer είναι γραμμένο στη γλώσσα Go και το δουλεύουμε από τη γραμμή εντολών. Έτοιμα προς εκτέλεση binaries για όλα τα γνωστά λειτουργικά συστήματα (αλλά και για αρκετές αρχιτεκτονικές), διατίθενται δωρεάν από την ιστοσελίδα της Hashicorp με τα downloads. Σε κάθε περίπτωση, μετά από μία σύντομη λήψη βρισκόμαστε με ένα νέο ZIP archive, το οποίο απλά αποσυμπιέζουμε και παίρνουμε ένα μόνο εκτελέσιμο: το packer. Καλό είναι να το μεταφέρουμε σε κάποιον κατάλογο που περιλαμβάνεται στη μεταβλητή PATH. Εμείς, π.χ., στο Mac OS X αλλά και στο Linux προτιμάμε να έχουμε το εκτελέσιμο του Packer κάτω από τον κατάλογο bin, στο home directory του χρήστη μας. Αν όλα είναι όπως πρέπει, δίνοντας στο packer την εντολή version βλέπουμε την έκδοση του Packer που διαθέτουμε:

mbpr15:~ cvar$ packer version
Packer v1.2.3

Σημείωση. Αν θέλετε την πλέον πρόσφατη, υπό ανάπτυξη έκδοση του Packer, τότε θα χρειαστεί να το μεταγλωττίσετε ξεκινώντας από τον πηγαίο κώδικα. Αναλυτικές οδηγίες θα βρείτε στο Github, συγκεκριμένα στην ενότητα για την προετοιμασία της Go.

Απαραίτητες έννοιες
Το Packer καταλαβαίνει έναν περιορισμένο αριθμό εντολών (commands). Η version, που του δώσαμε πριν λίγο, είναι μία από αυτές. Προκειμένου να δούμε όλες τις διαθέσιμες εντολές, στο τερματικό μας γράφουμε packer --help:

mbpr15:~ cvar$ packer --help
Usage: packer [--version] [--help] <command> [<args>]

Available commands are:
    build       build image(s) from template
    fix         fixes templates from old versions of packer
    inspect     see components of a template
    push        push a template and supporting files to a Packer build [...]
    validate    check that a template is valid
    version     Prints the Packer version

Η βασικότερη εντολή είναι η build, με την οποία φτιάχνουμε ένα ή περισσότερα machine images ξεκινώντας από ένα πρότυπο ή αλλιώς template. Τα templates του Packer είναι αρχεία JSON (JavaScript Object Notation), που καθορίζουν ένα ή περισσότερα builds. Τώρα, πριν σκεφτείτε (δικαίως) ότι οι ορισμοί μας είναι κάπως κυκλικοί, ας εξηγήσουμε εδώ ότι τα builds περιγράφουν εργασίες παραγωγής machine images για συγκεκριμένες πλατφόρμες. Σε πολύ λίγο θα δουλέψουμε με ένα template, εντός του οποίου περιγράφονται τρία builds: ένα για την παραγωγή machine image κατάλληλου για το VMware, ένα για την παραγωγή machine image κατάλληλου για το VirtualBox, κι άλλο ένα για την παραγωγή machine image κατάλληλου για το QEMU.

Τα αποτελέσματα ενός μεμονωμένου build ονομάζονται artifacts. Ουσιαστικά, πρόκειται για ένα σύνολο αρχείων (ή IDs, στην περίπτωση cloud platforms) που απαρτίζουν (ή καθορίζουν) μία εικονική μηχανή. Δύο παραδείγματα: Ένα build για το QEMU θα έχει ως artifact ένα αρχείο QCOW2, ενώ ένα build για το VMware θα έχει ως artifact έναν κατάλογο με αρχεία που περιγράφουν το VM κι απαρτίζουν το δίσκο του.

Τα υποσυστήματα του Packer που καταλαβαίνουν και διεκπεραιώνουν τα διάφορα builds, ονομάζονται builders. Το Packer, μ’ άλλα λόγια, διαθέτει builders για machine images τύπου VMware, VirtualBox, QEMU, OpenStack κ.ο.κ. Υπόψιν πως αν και το Packer έχει ήδη ένα μεγάλο πλήθος builders, την ίδια στιγμή δέχεται και νέους (ως plugins).

Μετά από ένα επιτυχημένο build, ο χρήστης του Packer είναι σε θέση να τρέξει έναν ή περισσότερους provisioners. Στο πλαίσιο της παρουσίασής μας θα δούμε έναν απλό provisioner με τη μορφή BASH script. Δουλειά του είναι να αφαιρεί το offline repository που χρησιμοποιεί ο installer του openSUSE, να προσθέτει online repositories της επιλογής μας και, τέλος, να εφαρμόζει στο λειτουργικό όλα τα διαθέσιμα patches και updates. Οι provisioners δεν είναι κατ’ ανάγκη shell scripts, αφού το Packer γνωρίζει και περί Salt, Ansible, Chef και άλλων configuration management systems.

Στο σημείο αυτό αξίζει ν’ αναφερθούμε και στους post-processors. Πρόκειται για εξαρτήματα του Packer που επιδρούν στα αποτελέσματα ενός build (ή ενός άλλου post-processor), και συνεπώς παράγουν νέα artifacts. Για παράδειγμα, μετά από επιτυχή ολοκλήρωση ενός build ίσως θέλουμε να συμπιέσουμε τα artifacts ή/και να τα ανεβάσουμε σε έναν απομακρυσμένο server.

Προετοιμασία περιβάλλοντος δοκιμών
Κατά την ενασχόλησή μας με το Packer προετοιμάσαμε ένα template με τρία διαφορετικά builds. Φυσικά, όλα τους παράγουν artifacts που απαρτίζουν εικονικές μηχανές. Κάθε μηχανή φιλοξενεί μια ελαφριά, server-oriented εγκατάσταση του openSUSE Leap 42.3, με εφαρμοσμένα όλα τα διαθέσιμα patches και updates. Τα builds είναι τρία, διότι κάθε μηχανή προορίζεται για ξεχωριστό hypervizor. Συγκεκριμένα, έχουμε builds για machine images που τρέχουν σε VirtualBox, σε VMware και σε QEMU.

Το εν λόγω template δεν το γράψαμε από την αρχή. Αντίθετα, από το openSUSE/Vagrant repository πήραμε το αρχείο 42.3-x86_64.json και το τροποποιήσαμε ελαφρώς. Γενικότερα, κάθε φορά που σχεδιάζετε να γράψετε ένα καινούργιο template για το Packer, αντί να ξεκινάτε από μηδενική βάση είναι καλή ιδέα να ρίχνετε πρώτα μια ματιά για το τι υπάρχει Εκεί Έξω(TM). Πάντως κι από το μηδέν να ξεκινήσετε με τη δημιουργία template, άπαξ και κατανοήσετε την εσωτερική λογική των αρχείων JSON δεν θα έχετε κανένα πρόβλημα απολύτως.

Πηγαίνετε τώρα στο GitHub και κλωνοποιήστε ή απλά κατεβάστε το repository που ετοιμάσαμε για το παρόν άρθρο. Για κλωνοποίηση, χρησιμοποιήστε το git:

mbpr15:github cvar$ git clone \
> https://github.com/colder-is-better/packer-templates.git
Cloning into 'packer-templates'...
remote: Counting objects: 37, done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 37 (delta 14), reused 27 (delta 7), pack-reused 0
Unpacking objects: 100% (37/37), done.

Εναλλακτικά, κατεβάστε μια συμπιεσμένη εκδοχή του repository κι αποσυμπιέστε το σχετικό αρχείο ZIP μέσα σε κάποιον κατάλογο που σας βολεύει. Ασχέτως αν κλωνοποιήσετε ή κατεβάσετε κι αποσυμπιέσετε, θα πάρετε τον υποκατάλογο packer-templates και κάτω απ’ αυτόν τους εξής καταλόγους κι αρχεία:

mbpr15:packer-templates cvar$ tree
.
├──  LICENSE
├── README.md
├── autoyast
│   ├── leap423srv.ai.libvirt.xml
│   └── leap423srv.ai.vmware.xml
├── provisioners
│   └── leap423srv.prov.sh
└── templates
    └── leap423srv.json

3 directories, 6 files

Με τον text editor της προτίμησής σας ανοίξτε τώρα το αρχείο leap423srv.json, από τον κατάλογο templates. Δεν χρειάζεται να γνωρίζετε πώς ακριβώς συντάσσονται τα αρχεία JSON, ώστε να καταλάβετε τι συμβαίνει μ’ αυτό που μόλις ανοίξατε. Κρίνουμε ωστόσο ότι καλό είναι να προηγηθεί μια σχετική παρουσίαση. Αμέσως μετά, το leap423srv.json θα βγάζει πολύ περισσότερο νόημα.

Τα αρχεία JSON, γενικά
Τα αρχεία JSON είναι text files με κατάληξη .json που χρησιμοποιούνται για τη διαμοίραση δεδομένων. Περιγράφουν αντικείμενα (objects), τα οποία στο συγκεκριμένο νοηματικό πλαίσιο αποτελούν μη-ταξινομημένες συλλογές ζευγών της μορφής όνομα/τιμή (name/value).

Τα ονόματα συχνά αναφέρονται κι ως κλειδιά (keys), είναι αλφαριθμητικά (strings) και περικλείονται εντός διπλών εισαγωγικών. Οι τιμές που δέχονται –ή καλύτερα οι τιμές που τους αντιστοιχίζονται– επιτρέπεται να είναι αριθμοί, άλλα αλφαριθμητικά (εντός διπλών εισαγωγικών), λογικές τιμές (δηλαδή true ή false, χωρίς εισαγωγικά), ταξινομημένες λίστες καμίας ή περισσοτέρων τιμών (arrays), κάποιο άλλο αντικείμενο ή η κενή τιμή (null, δύο διπλά εισαγωγικά).

Τα ζεύγη της μορφής όνομα/τιμή χωρίζονται μεταξύ τους με κόμματα, ενώ στα δεξιά ενός ονόματος ακολουθεί πάντα η άνω κάτω τελεία (:) και μετά έχουμε την αντίστοιχη τιμή. Μέσα σε κάθε αντικείμενο, ένα όνομα επιτρέπεται να εμφανίζεται το πολύ μία φορά.

Τα αντικείμενα οριοθετούνται εντός αγκίστρων: συγκεκριμένα, κάθε αντικείμενο αρχίζει με αριστερό άγκιστρο ({) και τελειώνει με δεξιό άγκιστρο (}). Ακριβώς επειδή τα αντικείμενα απαρτίζονται από ζεύγη της μορφής κλειδί/τιμή, λέμε ότι η μορφή δεδομένων (data format) των αντικειμένων είναι τύπου key-value.

Οι ταξινομημένες λίστες (στο εξής απλά λίστες) οριοθετούνται εντός αγκυλών. Αναλυτικότερα, μια λίστα αρχίζει με αριστερή αγκύλη ([) και τελειώνει με δεξιά αγκύλη (]). Αξίζει να υπογραμμιστεί ότι οι τιμές των λιστών, οι οποίες επίσης χωρίζονται μεταξύ τους με κόμματα, επιτρέπεται να είναι οποιουδήποτε τύπου. Έτσι, μπορούμε να έχουμε λίστες που περιλαμβάνουν αλφαριθμητικά, αριθμούς, λογικές τιμές, κενές τιμές, αντικείμενα ή/και άλλες λίστες.

Τα πρότυπα του Packer, υπό νέο φως
Ας στρέψουμε ξανά την προσοχή μας στο αρχείο leap423srv.json, από τον κατάλογο templates. (Το έχετε ήδη ανοικτό σε κάποιον text editor και το κοιτάζετε, έτσι δεν είναι;) Αν και τα αρχεία JSON κάλλιστα μπορούν να γράφονται σε μία μόνο γραμμή και χωρίς να έχει σημασία η χρήση whitespace, για λόγους ευκολότερης ανάγνωσης συχνά τα γράφουμε ώστε να εκτείνονται σε περισσότερες από μία γραμμές. Επίσης, με κατάλληλη χρήση whitespace οριοθετούμε οπτικά το πού αρχίζει και πού τελειώνει ένα αντικείμενο ή μία λίστα, καθώς κι αν ένα αντικείμενο ή λίστα αποτελεί τιμή άλλου αντικειμένου ή λίστας.

Σε μια πρώτη ανάγνωση, λοιπόν, στο αρχείο leap423srv.json διακρίνουμε ένα αντικείμενο με τρία ζεύγη της μορφής όνομα/τιμή.

  • Το ζεύγος που συναντάμε πρώτο από πάνω έχει όνομα variables και τιμή ένα αντικείμενο.

  • Στο επόμενο ζεύγος ως όνομα έχουμε το builders, στο οποίο είναι αντιστοιχισμένη μια λίστα από, σωστά μαντέψατε, τρία αντικείμενα.

  • Στο τρίτο και τελευταίο ζεύγος συναντάμε το όνομα provisioners, στο οποίο είναι αντιστοιχισμένη μία λίστα με ένα μόνο αντικείμενο.

Τα αλφαριθμητικά variables, builders και provisioners δεν έχουν κάποια σημασία στο σύμπαν των αρχείων JSON, αλλά δεν χρειάζεται να μας πιστέψετε. Αντίθετα, προτείνουμε να αντιγράψετε κάπου το αρχείο leap423srv.json και στο αντίγραφο να αλλάξετε τα variables, builders και provisioners, π.χ., σε eyjafjallajökull, hallgrímskirkja και hrunamannahreppur αντίστοιχα. Μετά πηγαίνετε στο δικτυακό τόπο του JSON validator ονόματι JSONLint. Εντός του μεγάλου πλαισίου κειμένου αντιγράψτε όλο το περιεχόμενο του τροποποιημένου αρχείου JSON και ύστερα πατήστε στο κουμπί “Validate JSON”. Μέσα σε ένα πράσινο πλαίσιο, κάτω από το κουμπί που μόλις πατήσατε, θα δείτε το μήνυμα “Valid JSON”.

Συντακτικός έλεγχος του τροποποιημένου JSON template μας, με τη βοήθεια του JSONLint. Για το JSON τα ονόματα των κλειδιών δεν έχουν κάποια σημασία, το Packer όμως έχει τις προτιμήσεις του (για καλό λόγο).

Συντακτικός έλεγχος του τροποποιημένου JSON template μας, με τη βοήθεια του JSONLint. Για το JSON τα ονόματα των κλειδιών δεν έχουν κάποια σημασία, το Packer όμως έχει τις προτιμήσεις του (για καλό λόγο).

Ενώ όμως τα λεκτικά των ονομάτων δεν έχουν σημασία για το JSON, για το Packer έχουν. Πράγματι, αλλάξτε το variables, π.χ., σε the_variables, μεταβείτε στον κατάλογο templates, μετά δώστε στο packer την εντολή validate ακολουθούμενη από το πλήρες όνομα του προτύπου:

mbpr15:templates cvar$ packer validate leap423srv.json
Failed to parse template: 1 error(s) occurred:
* Unknown root level key in template: 'the_variables'

Το Packer παραπονιέται, εξηγώντας ότι δεν ξέρει κάποιο root level key με το όνομα the_variables. Αλλάξτε το the_variables σε variables κι ελέγξτε ξανά την εγκυρότητα του leap423srv.json:

mbpr15:templates cvar$ packer validate leap423srv.json
Template validated successfully.

Πολύ ωραία, όλα καλά τώρα. Καταλαβαίνουμε βεβαίως σε τι αφορούν αυτά τα root level keys (variables, builders και provisioners), χρειάζεται ωστόσο να δούμε καλύτερα τι συμβαίνει με τις τιμές των τριών αυτών ονομάτων (ή κλειδιών).

Ανάλυση προτύπου
Στο κλειδί variables αντιστοιχίζεται ένα αντικείμενο, εντός του οποίου ορίζουμε μεταβλητές που θα χρησιμοποιηθούν στη συνέχεια από τους builders. Ο ορισμός και η ανάθεση τιμών στις μεταβλητές γίνεται με τη βοήθεια ζευγών κλειδιών/τιμών. Ιδού το κλειδί variables και το αντίστοιχο αντικείμενο (για οικονομία χώρου δεν παραθέτουμε τα πλήρη URLs):

"variables":
{
  "dist_ver": "42.3",

  "iso_url": "http://.../openSUSE-Leap-42.3-NET-x86_64.iso",
  "iso_checksum_url": "http://.../openSUSE-Leap-42.3-NET-x86_64.iso.sha256",
  "iso_checksum_type": "sha256",

  "out_dir_prefix": "/tmp/packer_out",

  "memory": "2048",
  "cpus": "2",
  "os_disk_size": "131072",

  "fusion_path": "/Applications/VMware Fusion.app"
}

Η μεταβλητή dist_ver δεν είναι απαραίτητη για τη λειτουργία του Packer. Την ορίζουμε όμως για τη δική μας ευκολία και τη θέτουμε ίση με την έκδοση του openSUSE Leap που θα εγκατασταθεί στα machine images.

Στη δε μεταβλητή iso_url αντιστοιχίζουμε ένα URL για το κατέβασμα του ISO image, από το οποίο θα γίνεται η εγκατάσταση του openSUSE Leap σε καθένα από τα τρία υποστηριζόμενα machine images. Μιας και προσανατολιζόμαστε σε λιτές και server-oriented εγκαταστάσεις, αντί για το πλήρες DVD image κατεβάζουμε το κατά πολύ μικρότερο NET image (εγκατάσταση από το δίκτυο). Μία λίστα με mirrors για τα images του openSUSE Leap θα βρείτε στη σχετική ιστοσελίδα της διανομής. Αν θέλετε, λοιπόν, αντί να κατεβάσετε το image από το Erlangen, όπως κάνουμε εμείς, τροποποιήστε την τιμή της iso_url ώστε να το λαμβάνετε από έναν γεωγραφικά εγγύτερο server ή από κάποιον δικό σας. Στη μεταβλητή iso_checksum_url, εξάλλου, δίνουμε το URL προς ένα αρχείο με το SHA256 hash του ISO image που κατεβάζουμε. Αυτό γίνεται ώστε, μετά την ολοκλήρωση του download και πριν την εγκατάσταση, να πραγματοποιηθεί έλεγχος ακεραιότητας για το νέο αρχείο ISO που έχουμε τοπικά. Εναλλακτικά, αντί της iso_checksum_url μπορούμε να χρησιμοποιήσουμε τη μεταβλητή iso_checksum και να τις αναθέσουμε απευθείας το ίδιο το hash. Σε κάθε περίπτωση, στη μεταβλητή iso_checksum_type χρειάζεται ν’ αναθέσουμε το είδος του hash. Οι υποστηριζόμενες τιμές είναι: none, md5, sha1, sha256 και sha512. Με την τιμή none το checksumming παραλείπεται, ωστόσο κάτι τέτοιο δεν προτείνεται.

Οι μεταβλητές iso_url, iso_checksum_urliso_checksum) και iso_checksum_type είναι απαραίτητες για τους τρεις builders που περιγράφουμε στη συνέχεια. Σημειώστε ότι το URL για την iso_url επιτρέπεται να οδηγεί κάπου τοπικά — συγκεκριμένα σε κάποιο αρχείο ISO που έχουμε ήδη στον υπολογιστή μας. Αν δεν συμβαίνει κάτι τέτοιο, την πρώτη φορά που το Packer θα κατεβάσει το ISO image θα το κρατήσει για επόμενες εκτελέσεις ή/και για άλλους builders.

Συνεχίζουμε με τη μεταβλητή out_dir_prefix. Σε αυτή αναθέτουμε μια τοπική διαδρομή, κάτω από την οποία θα αποθηκεύονται τα artifacts μετά από κάθε επιτυχημένο build.

Στη μεταβλητή memory, η οποία αφορά στους builders, αναθέτουμε την τιμή 2048. Αυτό το κάνουμε διότι οι αντίστοιχες εικονικές μηχανές θέλουμε να έχουν 2GiB μνήμης RAM. Βέβαια ο builder για το QEMU μάς δίνει ένα μόνο QCOW2 image file, χωρίς κάποιο συνοδευτικό αρχείο XML που περιγράφει το hardware της μηχανής. Το αρχείο αυτό παράγεται αργότερα, π.χ., όταν με τη βοήθεια του Virtual Machine Manager φτιάχνουμε ένα νέο VM με δίσκο το QCOW2 που παρήγαγε το Packer. Στην περίπτωση του QEMU, λοιπόν, η μεταβλητή memory υποδεικνύει την ανώτερη ποσότητα μνήμης (σε GiB) που θα δεσμεύσει ο builder κατά τη δημιουργία του σχετικού QCOW2.

Ανάλογες παρατηρήσεις ισχύουν και για τη μεταβλητή cpus (αριθμοί πυρήνων επεξεργαστή), η οποία στο template μας αφορά στους builders των VMware και VirtualBox (κι αγνοείται από εκείνον του QEMU).

Στη δε μεταβλητή os_disk_size αποδίδουμε το μέγεθος του δίσκου εγκατάστασης, σε MiB. Στο πρότυπό μας πηγαίνουμε για δίσκο μεγέθους 131072MiB ή αλλιώς 128GiB. Υπόψιν ότι το σχετικό αρχείο ή αρχεία που θα δημιουργηθούν δεν θα καταλαμβάνουν όλον αυτό τον χώρο εξ αρχής. Αντίθετα, θα πρόκειται για sparse file ή sparse files κι ο πραγματικός χώρος που θα καταλαμβάνουν θα μεγαλώνει δυναμικά. Αυτά τα 128GiB, μ’ άλλα λόγια, αποτελούν άνω όριο μεγέθους.

Η τελευταία μεταβλητή που ορίζεται εντός του αντικειμένου για το κλειδί variables, είναι η fusion_path: της αναθέτουμε τη διαδρομή του VMware Fusion — και φυσικά αφορά μόνο στον builder για τον ομώνυμο desktop hypervizor.

Προχωράμε τώρα στους τρεις builders, οι οποίοι ορίζονται από ισάριθμα αντικείμενα. Όλα μαζί απαρτίζουν ένα array που αντιστοιχίζεται στο root level key ονόματι builders. Διαφορετικά: Στο κλειδί builders αντιστοιχίζεται ένα array που αποτελείται από τρία αντικείμενα. Καθένα από αυτά τα αντικείμενα ορίζει ένα διαφορετικό build. Παρεμπιπτόντως, όποτε θέλετε να βλέπετε στα γρήγορα τις μεταβλητές, τους builders και τους provisioners ενός template, αρκεί να πληκτρολογείτε κάτι τέτοιο:

mbpr15:templates cvar$ packer inspect leap423srv.json
Optional variables and their defaults:

  cpus              = 2
  dist_ver          = 42.3
  fusion_path       = /Applications/VMware Fusion.app
  iso_checksum_type = sha256
  iso_checksum_url  = http://.../openSUSE-Leap-42.3-NET-x86_64.iso.sha256
  iso_url           = http://.../openSUSE-Leap-42.3-NET-x86_64.iso
  memory            = 2048
  os_disk_size      = 131072
  out_dir_prefix    = /tmp/packer_out

Builders:

  libvirt    (qemu)
  virtualbox (virtualbox-iso)
  vmware     (vmware-iso)

Provisioners:

  shell

Note: If your build names contain user variables or template
functions such as 'timestamp', these are processed at build time,
and therefore only show in their raw form here.

Ας συζητήσουμε αναλυτικά για τον πρώτο builder, που ορίζει πώς δημιουργείται ένα machine image για το VirtualBox. Για λόγους ευκολότερης αναφοράς, παραθέτουμε εδώ το σχετικό αντικείμενο (η πλήρης διαδρομή προς το AutoYaST profile είναι περικομμένη):

{
  "name": "virtualbox",
  "type": "virtualbox-iso",

  "iso_url": "{{user `iso_url`}}",
  "iso_checksum_url": "{{user `iso_checksum_url`}}",
  "iso_checksum_type": "{{user `iso_checksum_type`}}",

  "output_directory": "{{user `out_dir_prefix`}}/oSUSE-Leap-{{user `dist_ver`}}-{{build_name}}",

  "vm_name": "osuse-leap-{{user `dist_ver`}}",
  "guest_os_type": "OpenSUSE_64",
  "guest_additions_mode": "disable",
  "hard_drive_interface": "sata",
  "disk_size": "{{user `os_disk_size`}}",

  "vboxmanage": [
    [ "modifyvm", "{{.Name}}", "--memory", "{{user `memory`}}" ],
    [ "modifyvm", "{{.Name}}", "--cpus", "{{user `cpus`}}" ],
    [ "modifyvm", "{{.Name}}", "--audio", "none" ],
    [ "modifyvm", "{{.Name}}", "--usb", "off" ],
    [ "modifyvm", "{{.Name}}", "--rtcuseutc", "on" ]
  ],

  "ssh_username": "root",
  "ssh_password": "topsecret",
  "ssh_wait_timeout": "90m",
  "ssh_port": 22,

  "boot_command": [
    "<esc><enter><wait>",
    "linux ",
    "lang=en_US ",
    "textmode=1 ",
    "autoyast2=https://.../leap423srv.ai.vmware.xml",
    "<enter>"
  ],

  "shutdown_command": "shutdown -P now",

  "headless": true
}

Το όνομα του builder είναι virtualbox (βλέπε κλειδί name) και, επειδή στην πραγματικότητα υπάρχουν δύο είδη builders για VirtualBox machine images, στο κλειδί type από κάτω αντιστοιχίζουμε εκείνον τον τύπο που μας ενδιαφέρει.

Αναλυτικότερα, οι επιτρεπτές τιμές για το type είναι οι virtualbox-iso και virtualbox-ovf. Με τον τύπο virtualbox-iso ξεκινάμε από ένα ISO image, το οποίο χρησιμοποιείται για την εγκατάσταση guest OS σε ένα ολοκαίνουργιο VM. Μετά την εγκατάσταση καλούνται οι όποιοι provisioners και, τέλος, παράγονται τα artifacts που απαρτίζουν μια πλήρη εικονική μηχανή για το VirtualBox. Με τον δε τύπο virtualbox-ovf ξεκινάμε από ένα αρχείο OVF ή OVA (αμφότερα περιγράφουν πλήρεις εικονικές μηχανές του VirtualBox), τρέχουμε έναν ή περισσότερους provisioners και μετά δημιουργούμε ένα νέο machine image. Στο πλαίσιο του παρόντος άρθρου ξεκινάμε από μηδενική βάση, γι’ αυτό και στρεφόμαστε στον builder τύπου virtualbox-iso.

Στις τρεις επόμενες γραμμές, οι μεταβλητές iso_url, iso_checksum_url και iso_checksum_type λαμβάνουν τις τιμές των ομώνυμων μεταβλητών του αντικειμένου που έχει ανατεθεί στο κλειδί variables, παραπάνω. Θα αναρωτιέστε –και με το δίκιο σας– γι’ αυτά τα διπλά άγκιστρα ({{ }}), όπως επίσης και για το περιεχόμενό τους. Τα πράγματα είναι απλά: ό,τι περιλαμβάνεται εντός διπλών αγκίστρων αξιολογείται κατά το χρόνο εκτέλεσης, δηλαδή από τη στιγμή που στο τερματικό μας θα δώσουμε κάτι σαν packer build template.json. Συγκεκριμένα, εντός των διπλών αγκίστρων βάζουμε συναρτήσεις που καταλαβαίνει το Packer — και καλύτερα να παραθέσουμε μερικά παραδείγματα:

  • build_name: Η συνάρτηση αυτή επιστρέφει το όνομα του τρέχοντα builder.

  • pwd: Επιστρέφει τον κατάλογο από τον οποίο εκλήθη το packer.

  • timestamp: Επιστρέφει το τρέχον UNIX timestamp, σε UTC.

  • uuid: Επιστρέφει ένα τυχαίο UUID (Universally Unique IDentifier)

  • user: Επιστρέφει την τιμή μιας μεταβλητής χρήστη.

Πλέον, λοιπόν, καταλαβαίνουμε πώς ακριβώς στον builder του VirtualBox –αλλά και στους άλλους δύο του template μας– αποδίδονται τιμές στις μεταβλητές iso_url, iso_checksum_url και iso_checksum_type, βάσει των τιμών που οι ίδιοι έχουμε αναθέσει στις ομώνυμες μεταβλητές του αντικειμένου για το root level key ονόματι variables.

Καταλαβαίνουμε, επίσης, πώς σχηματίζεται το path για το output_directory του εκάστοτε builder. Εντός αυτού του καταλόγου αποθηκεύονται τα artifacts. Στο παράδειγμά μας, η διαδρομή του path είναι απόλυτη. Αν ήταν σχετική, τότε θα ξεκινούσε από τον κατάλογο που βρισκόμασταν όταν τρέξαμε το Packer. Σε κάθε περίπτωση, έχετε υπόψη ότι ο κατάλογος στον οποίο αποθηκεύονται τα artifacts πρέπει να μην υπάρχει. Αν υπάρχει, το Packer παραπονιέται και τερματίζει το αντίστοιχο build. Δείτε:

PACKER_CACHE_DIR=/Users/cvar/packer_cache \
> packer build -only virtualbox leap423srv.json

virtualbox output will be in this color.

==> virtualbox: Downloading or copying ISO
    virtualbox: Found already downloaded, initial checksum matched, no [...]
Build 'virtualbox' errored: Output directory exists: /tmp/packer_out/[...]

Use the force flag to delete it prior to building.

==> Some builds didn't complete successfully and had errors:
--> virtualbox: Output directory exists: /tmp/packer_out/[...]

Use the force flag to delete it prior to building.

==> Builds finished but no artifacts were created.

Πάμε τώρα να δούμε τη λογική των τεσσάρων επόμενων μεταβλητών.

  • vm_name: Έχει το όνομα του αρχείου OVF της εικονικής μηχανής, χωρίς την κατάληξη.

  • guest_os_type: Έχει τον τύπο του guest OS που πρόκειται να εγκατασταθεί. Προκειμένου να δούμε όλους τους τύπους που υποστηρίζει το VirtualBox, σε ένα τερματικό απλά γράφουμε VBoxManage list ostypes.

  • guest_additions_mode: Δεν θέλουμε να κάνει κάτι το Packer με τα Guest Additions, γι’ αυτό και θέτουμε την τιμή του συγκεκριμένου κλειδιού ως disable. Κατά την εγκατάσταση του openSUSE, όταν ανιχνεύεται η παρουσία του VirtualBox τότε τα Guest Additions εγκαθίστανται αυτόματα.

  • hard_drive_interface: Έχει τον τύπο του ελεγκτή πάνω στον οποίο είναι συνδεδεμένος ο δίσκος εγκατάστασης του guest OS. Αποδεκτές τιμές για τη συγκεκριμένη μεταβλητή είναι οι ide, sata και scsi.

Το δε κλειδί vboxmanage, εξάλλου, αρχικά μοιάζει κάπως περίπλοκο, αλλά στην πραγματικότητα δεν είναι και τόσο. Παρατηρήστε ότι ως τιμή δέχεται μια λίστα, η οποία αποτελείται από λίστες αλφαριθμητικών. Κάθε μία από αυτές τις υπολίστες λαμβάνεται υπόψη και αξιολογείται με τη σειρά εμφάνισής της και χρησιμεύει ως συμπλήρωμα για το εργαλείο VBoxManage.

Αλλάζοντας οπτική, πληκτρολογώντας σε ένα τερματικό VBoxManage --help διαπιστώνουμε ότι το εργαλείο δέχεται μια σειρά από εντολές. Μία εξ αυτών είναι η modifyvm — και μ’ αυτό το αλφαριθμητικό αρχίζει κάθε υπολίστα. Έτσι, χάρη στην πρώτη υπολίστα είναι σαν να πληκτρολογούμε την ακόλουθη εντολή:

VBoxManage modifyvm osuse-leap-42.3 --memory 2048

Το osuse-leap-42.3 προκύπτει από την αξιολόγηση (χάρη στα {{ }}) της μεταβλητής προτύπου (template variable) με όνομα .Name. Οι template variables είναι μεταβλητές ειδικού σκοπού που αρχικοποιούνται αυτόματα από το Packer κατά το χρόνο εκτέλεσης. Όλες οι μεταβλητές του είδους ξεχωρίζουν, διότι αρχίζουν με τελεία. Στρέφοντας ξανά την προσοχή μας στην εντολή VBoxManage modifyvm osuse-leap-42.3 --memory 2048, να πούμε ότι, όπως πολύ σωστά υποθέσατε, το 2048 εκφράζει μέγεθος μνήμης σε MiB και προκύπτει από την αξιολόγηση του αποτελέσματος της συνάρτησης user επί της μεταβλητής memory. Δείτε τώρα πόσο γρήγορα βγάζουν νόημα οι υπολίστες της λίστας έχει ανατεθεί ως τιμή στο κλειδί vboxmanage. Ουσιαστικά, με την εν λόγω λίστα καθορίζουμε τα χαρακτηριστικά του νέου VM και συγκεκριμένα λέμε ότι:

  • θέλουμε να έχει 2GiB μνήμης RAM

  • θέλουμε να έχει επεξεργαστή με δύο πυρήνες

  • δεν θέλουμε να διαθέτει κύκλωμα ήχου

  • δεν θέλουμε να διαθέτει ελεγκτή USB

  • θέλουμε το ρολόι του συστήματος να είναι σε UTC

Στοιχηματίζουμε ότι έχετε ήδη εκτιμήσει τη δύναμη του Packer — και σκεφτείτε ότι προς το παρόν γνωρίζουμε λίγες μόνο από τις δυνατότητές του! Συνεχίστε το διάβασμα, με την επόμενη ομάδα μεταβλητών που ορίζονται στο αντικείμενο του builder για το VirtualBox.

  • ssh_username: Μετά την εγκατάσταση του guest OS θα αναμένουμε από τον provisioner μας να συνδεθεί μέσω SSH στο λογαριασμό ενός συγκεκριμένου χρήστη του συστήματος. Ε, αυτός ο “συγκεκριμένος χρήστης” θέλουμε να είναι ο root. Φυσικά, κατά την εγκατάσταση του openSUSE φροντίζουμε ώστε η υπηρεσία του SSH να ενεργοποιείται αυτόματα κάθε φορά που το λειτουργικό ξεκινά. Να υπογραμμίσουμε εδώ ότι η διαδικασία της εγκατάστασης και όλες οι επιλογές που την αφορούν, γίνονται εντελώς αυτοματοποιημένα. Το πώς ακριβώς θα το συζητήσουμε σε πολύ λίγο.

  • ssh_password: Ποιο είναι το password του root; Έχουμε φροντίσει ώστε να ορίζεται αυτόματα (όπως αυτόματα γίνεται όλη η εγκατάσταση) και να είναι topsecret (κατά γράμμα).

  • ssh_port: Ποιο είναι το port της υπηρεσίας του SSH; Εξ ορισμού είναι το 22/TCP, οπότε ως τιμή για το κλειδί ssh_port ορίζουμε το 22.

  • ssh_timeout: Πόσο πρέπει να περιμένει το Packer, πριν θεωρήσει ότι η υπηρεσία του SSH είναι διαθέσιμη; Αυτό θα σημαίνει ότι η εγκατάσταση του guest OS έχει ολοκληρωθεί, οπότε στο πλαίσιο του παραδείγματός μας ο shell provisioner θα είναι σε θέση να συνδεθεί στο σύστημα. Η εγκατάσταση μιας λιτής και server oriented εκδοχής του openSUSE Leap δεν διαρκεί πολύ. Λαμβάνοντας όμως υπόψη τις αργές δικτυακές συνδέσεις, αποφασίσαμε να θέσουμε στο συγκεκριμένο κλειδί την τιμή 90m (μιάμιση ώρα). Κατά πάσα πιθανότητα ποτέ δεν θα χρειαστεί να περιμένουμε τόσο πολύ, γεγονός που σημαίνει ότι η υπηρεσία του SSH θα γίνεται γρηγορότερα διαθέσιμη.

Ας δούμε τώρα το κλειδί boot_command, στο οποίο είναι αντιστοιχισμένη μια λίστα αλφαριθμητικών. Ουσιαστικά, σε αυτό το κλειδί δίνουμε όλα εκείνα τα πλήκτρα που πατάμε για να ξεκινήσει η εγκατάσταση του εκάστοτε guest OS — εν προκειμένω του openSUSE Leap 42.3.

Κάθε φορά που εγκαθιστούμε ένα λειτουργικό σύστημα χειροκίνητα, ξεκινάμε το VM (ή τον φυσικό υπολογιστή) και περιμένουμε μερικά δευτερόλεπτα έως ότου εμφανιστεί η αρχική οθόνη του installer. Το Packer εξ ορισμού περιμένει 10 δευτερόλεπτα πριν θεωρήσει ότι η εν λόγω οθόνη έχει εμφανιστεί. Στη συντριπτική πλειονότητα των περιπτώσεων αυτός ο χρόνος είναι αρκετός. Αν όμως για κάποιον λόγο δεν είναι, τότε πριν από το κλειδί boot_command επιβάλλεται να ορίσουμε το κλειδί boot_wait και να του αναθέσουμε, π.χ., την τιμή 20s (είκοσι δευτερόλεπτα). Μετά το πέρας αυτού του χρονικού διαστήματος, λοιπόν, αξιολογείται η τιμή του κλειδιού boot_command.

Σκεφτείτε τι κάνουμε όταν βλέπουμε τον Grub, από το μέσο εγκατάστασης του openSUSE. Γενικά, παρατηρούμε τις διαθέσιμες επιλογές και, επειδή συνήθως δεν θέλουμε να ξεκινήσουμε τον υπολογιστή από ήδη εγκατεστημένο λειτουργικό αλλά να εγκαταστήσουμε νέο, πατάμε το κάτω βελάκι του δρομέα ώστε να να φύγουμε από την πάνω-πάνω επιλογή, που λέει “Boot from Hard Disk”, και να μεταβούμε πάνω από την επόμενη, που λέει “Installation”. Αμέσως μετά είτε πατάμε κατευθείαν το πλήκτρο [Enter], ώστε να φορτώσει ο installer, είτε πριν πατήσουμε [Enter] γράφουμε πρώτα κάτι στη θυρίδα “Boot Options”, στο κάτω μέρος της οθόνης. Τι είναι αυτό το “κάτι” που έχει νόημα να γράψουμε εκεί; Πολλά πράγματα έχουν νόημα γι’ αυτή τη θυρίδα. Κάτι που τώρα –κι εντελώς στην τύχη– μας έρχεται κατά νου, είναι να πληκτρολογήσουμε μια εντολή που λέει ότι α) θέλουμε να πραγματοποιήσουμε αυτόματη εγκατάσταση με τη βοήθεια του συστήματος AutoYaST, καθώς και ότι β) έχουμε έτοιμο το αρχείο XML του αντίστοιχου AutoYaST profile. (Αν δεν έχετε ιδέα περί AutoYaST, διαβάστε το σχετικό μας άρθρο.)

Στο πλαίσιο της παρουσίασής μας, προκειμένου να κάνουμε αυτοματοποιημένες εγκαταστάσεις του openSUSE Leap στα machine images του Packer, ετοιμάσαμε δύο AutoYaST profiles. Αμφότερα βρίσκονται στον κατάλογο autoyast, εντός του καταλόγου packer-templates που έχετε ήδη κατεβάσει. Πρόκειται για τα αρχεία leap423srv.ai.libvirt.xml και leap423srv.ai.vmware.xml. Θα σκέφτεστε τώρα ότι λείπει το AutoYaST για machine images του VirtualBox. Όμως το αρχείο leap423srv.ai.vmware.xml κάνει τόσο για machine images του VMware, όσο και για machine images του VirtualBox. Το δε leap423srv.ai.libvirt.xml, από την άλλη, είναι κατάλληλο μόνο για machine images του QEMU. Οι διαφορές μεταξύ των δύο αρχείων είναι ελάχιστες, αλλά κρίσιμες: στο leap423srv.ai.vmware.xml τα device files των δίσκων αρχίζουν με το πρόθεμα sd, ενώ στο leap423srv.ai.libvirt.xml τα αντίστοιχα device files αρχίζουν με το πρόθεμα vd:

mbpr15:autoyast cvar$ diff leap423srv.ai.vmware.xml \
> leap423srv.ai.libvirt.xml
7c7
<       <append>resume=/dev/sda1 splash=silent quiet showopts</append>
---
>       <append>resume=/dev/vda1 splash=silent quiet showopts</append>
746c746
<       <device>/dev/sda</device>
---
>       <device>/dev/vda</device>

Να υπογραμμίσουμε ότι τα δύο αυτά AutoYaST profiles είναι διαθέσιμα και δικτυακά, με URLs που οδηγούν απευθείας στις εκδοχές τους στο GitHub:

leap423srv.ai.libvirt.xml -->
https://raw.githubusercontent.com/colder-is.../leap423srv.ai.libvirt.xml

leap423srv.ai.vmware.xml -->
https://raw.githubusercontent.com/colder-is.../leap423srv.ai.vmware.xml

Άρα δεν χρειαζόμαστε κάποιον web server, προκειμένου να υποδεικνύουμε στον installer του openSUSE τη θέση του κατάλληλου AutoYaST profile. Επίσης, δεν χρειάζεται να πληκτρολογούμε χειροκίνητα αυτά τα μακροσκελή URLs, αφού το Packer είναι παραπάνω από ικανό να πληκτρολογεί για εμάς. Πράγματι, δείτε ξανά το κλειδί boot_command και το array του:

"boot_command": [
  "<esc><enter><wait>",
  "linux ",
  "lang=en_US ",
  "textmode=1 ",
  "autoyast2=https://.../leap423srv.ai.vmware.xml",
  "<enter>"
]

Χάρη σ’ αυτό, το Packer κάνει τις ακόλουθες κινήσεις για εμάς:

  • Στην πρώτη οθόνη του installer, το Packer πατάει το πλήκτρο [ESC] (βλ. αυτό το <esc>).

  • Μετά το [ESC] εμφανίζεται ένα pop-up window, που προειδοποιεί ότι είμαστε έτοιμοι να αφήσουμε το περιβάλλον γραφικών και να μεταβούμε σε κονσόλα απλού κειμένου. Το focus είναι στο κουμπί “OK” κι όχι στο “Cancel”, οπότε το Packer επικυρώνει τη μετάβαση σε text console πατώντας [Enter] (βλ. <enter>)

  • Αμέσως πηγαίνουμε στην text console αλλά πριν το Packer πατήσει κάποιο άλλο κουμπί, καλού κακού περιμένει για ένα δευτερόλεπτο (βλ. το <wait>, μετά το <enter>).

  • Ακολούθως το Packer πληκτρολογεί linux κι αφήνει ένα κενό. Ουσιαστικά πρόκειται για μια εντολή που σηματοδοτεί την εκκίνηση της εγκατάστασης. Το Packer, όμως, δεν πατάει ακόμη το [Enter].

  • Μετά το linux και το κενό, το Packer πληκτρολογεί lang=en_US (η γλώσσα του περιβάλλοντος εγκατάστασης είναι η αγγλική), αφήνει άλλο ένα κενό, πληκτρολογεί textmode=1 (η εγκατάσταση να γίνει σε περιβάλλον απλού κειμένου) κι αφήνει άλλο ένα κενό.

  • Στη συνέχεια το Packer πληκτρολογεί autoyast2=https://raw.githubusercontent.com/colder-is-better/packer-templates/master/autoyast/leap423srv.ai.vmware.xml. Για το YaST, το οποίο αναλαμβάνει την εγκατάσταση του openSUSE, όλο αυτό σημαίνει ότι η εγκατάσταση θα γίνει αυτόματα, μέσω του συστήματος AutoYaST. Επίσης, το YaST ξέρει το πλήρες URL προς το ίδιο το AutoYaST profile.

  • To Packer, τέλος, πατάει ένα [Enter] (βλ. <enter>). Αυτή η ενέργεια σηματοδοτεί την εκκίνηση της αυτοματοποιημένης διαδικασίας εγκατάστασης.

Αξίζει να σημειώσουμε ότι οι εγκαταστάσεις του Packer δεν χρειάζεται να γίνονται υποχρεωτικά με αυτοματοποιημένο τρόπο. Κάλλιστα θα μπορούσαμε να βάλουμε το Packer να ξεκινήσει τον installer και μετά ν’ αναλάβουμε εμείς. Κάτι, τέτοιο, φυσικά, είναι κάπως ασύμβατο με την όλη λογική του Packer. Ευτυχώς, παρόμοια συστήματα εγκατάστασης υπάρχουν και για άλλες διανομές του Linux. Για παράδειγμα, στα CentOS και Fedora έχουμε το Kickstart, ενώ στα Debian και Ubuntu έχουμε το [Preseed] (https://en.wikipedia.org/wiki/Preseed).

Όταν η εγκατάσταση του openSUSE ολοκληρωθεί και η υπηρεσία του SSH γίνει διαθέσιμη, το Packer θα τρέξει όποιους provisioners έχουμε ορίσει (βλ. παρακάτω). Αφού τελειώσουν κι αυτοί τη δουλειά τους, το Packer θα κλείσει τη μηχανή εκτελώντας την εντολή που έχει ανατεθεί στο κλειδί shutdown_command.

Και μια τελευταία παρατήρηση. Αναλόγως της τιμής αληθείας (true ή false) που έχουμε αναθέσει στο κλειδί headless του αντικειμένου για τον builder του VirtualBox, κατά την εγκατάσταση του λειτουργικού δεν θα βλέπουμε την πρόοδο της διαδικασίας σε ξεχωριστό παράθυρο ή θα τη βλέπουμε. Για νέα templates που φτιάχνετε, κατά το στάδιο των δοκιμών προτείνουμε στο headless να αναθέτετε την τιμή false. Αφού βεβαιωνόσαστε ότι όλα δουλεύουν όπως πρέπει, τότε θα αλλάζετε την τιμή του headless σε true.

Ένας απλός shell provisioner για το template μας
Όταν η εγκατάσταση guest OS στο machine image ολοκληρωθεί και το VM επανεκκινήσει, το Packer ελέγχει αν υπάρχουν provisioners που πρέπει να εκτελέσει. Στο πρότυπό μας ορίζουμε έναν απλό shell provisioner, ο οποίος φροντίζει για την αλλαγή αποθετηρίων λογισμικού και την αναβάθμιση του λειτουργικού συστήματος. Δείτε το κλειδί provisioners:

"provisioners": [
    {
      "type": "shell",
      "pause_before": "90s",
      "expect_disconnect": true,
      "script": "../provisioners/leap423srv.prov.sh"
    }
  ]

Γενικά, ως τιμή δέχεται μία λίστα αντικειμένων. Η δική μας λίστα αριθμεί ένα μόνο αντικείμενο, αφού έχουμε ετοιμάσει έναν μόνο provisioner. Ας δούμε εν συντομία τα κλειδιά του εν λόγω αντικειμένου.

  • type: Εδώ δηλώνεται το είδος του provisioner — εκείνος του παραδείγματός μας είναι ένα απλό shell script (για το BASH).

  • pause_before: Πόσο πρέπει να περιμένει το Packer πριν εκτελέσει τον πρώτο ή τον επόμενο provisioner; Πρέπει να περιμένει κι αν ναι γιατί; Δεν υπάρχει μία απάντηση για κάθε πιθανό σενάριο. Γι’ αυτό που παρουσιάζουμε, όμως, το Packer δεν πρέπει να εκτελέσει τον shell provisioner αμέσως μόλις ενεργοποιηθεί η υπηρεσία του SSH. Πρέπει να περιμένει λίγο — και μετά από σχετικούς πειραματισμούς έχουμε αποφασίσει ότι αυτό το “λίγο” αρκεί να είναι 90 δευτερόλεπτα. Πράγματι, όπως ήδη αναφέραμε η εγκατάσταση του openSUSE Leap γίνεται εντελώς αυτοματοποιημένα, με τη βοήθεια του συστήματος του AutoYaST. Μετά τη διαμόρφωση του συστήματος (δίσκοι, κατατμήσεις, δικτύωση, χρήστες, επιλογή patterns λογισμικού κ.ο.κ.), κι έπειτα από το κατέβασμα και την εγκατάσταση όλων των απαραίτητων πακέτων, το AutoYaST επανεκκινεί το σύστημα και πριν μας το παραδώσει εκτελεί μερικές ακόμη εργασίες διαχείρισης. Σ’ αυτό το δεύτερο και τελευταίο στάδιο της εγκατάστασης, η υπηρεσία του SSH είναι διαθέσιμη. Το Packer, όμως, δεν πρέπει να επιχειρήσει σύνδεση κι εκτέλεση του provisioner. Αφενός η εγκατάσταση δεν έχει ακόμα ολοκληρωθεί, αφετέρου το AutoYaST θα εκτελέσει μια τελευταία επανεκκίνηση πριν ολοκληρώσει την εργασία του. Αν, λοιπόν, επιχειρηθεί σύνδεση κι εκτέλεση του shell provisioner, τότε και αυτός δεν πετύχει το σκοπό του αλλά και το Packer θα αποτύχει και δεν θα παράξει τα επιθυμητά artifacts. Όλη η δουλειά που έγινε προηγουμένως θα πάει εντελώς χαμένη. Η λύση, λοιπόν, είναι να αναθέσουμε μια κατάλληλη τιμή στο κλειδί pause_before, ώστε το Packer να μη συνδεθεί αμέσως στο VM μέσω SSH.

  • expect_disconnect: Σε περίπτωση που ο SSH server ή το guest OS επανεκκινήσει, αυτή η ενέργεια δεν θέλουμε να θεωρηθεί ως σφάλμα. Γι’ αυτό και στο συγκεκριμένο κλειδί θέτουμε την τιμή true.

  • script: Ορίζουμε τη διαδρομή (path) προς το script που θα εκτελεστεί ως provisioner στο guest OS. Στο παράδειγμά μας το path είναι σχετικό ως προς τον κατάλογο που περιλαμβάνει το template.

Το ίδιο το script είναι το αρχείο leap423srv.prov.sh και βρίσκεται μέσα στον κατάλογο με όνομα provisioners. Δεν το αναλύουμε –δεν είναι και τόσο περίπλοκο–, απλά αναφέρουμε τι ακριβώς κάνει:

  • Αφαιρεί το offline repository που προστέθηκε στο σύστημα κατά την εγκατάσταση του openSUSE Leap 42.3.

  • Προσθέτει τέσσερα νέα online repositories για OSS και Non-OSS πακέτα, καθώς και για τα updates τους. Όπως μπορείτε να δείτε από τις μεταβλητές REPO_OSS, REPO_NON_OSS, REPO_OSS_UPDATES και REPO_NON_OSS_UPDATES, χρησιμοποιούμε mirrors από το Πανεπιστήμιο του Erlangen. Πιθανώς θα θελήσετε mirrors σε άλλη γεωγραφική περιοχή, οπότε τροποποιήστε κατάλληλα τις τέσσερις αυτές μεταβλητές. Διαλέξτε mirrors από τη σελίδα με τα openSUSE Download Mirrors για το Leap 42.3.

  • Στη συνέχεια, το script φρεσκάρει τα repositories και εφαρμόζει τα όποια διαθέσιμα patches. Ίσως αναβαθμιστεί το ίδιο το zypper (εντάξει, σίγουρα θα αναβαθμιστεί), οπότε σ’ αυτή την περίπτωση η διαδικασία εφαρμογής patches σταματά κι αρχίζει εκ νέου.

  • Τέλος, το script αναβαθμίζει τυχόντα πακέτα για τα οποία υπάρχει νεότερη έκδοση αλλά όχι κάποιο patch (π.χ., περίπτωση αλλαγής αρχιτεκτονικής).

Μετά από όλη αυτή την εκτενέστατη –κι ελπίζουμε όχι ιδιαίτερα κουραστική– συζήτηση, νομίζουμε ότι έχει φτάσει επιτέλους η στιγμή που θα δούμε το Packer επί το έργον.

Στον κόσμο του Packer κάποια SSH connections δεν πρέπει να γίνονται βιαστικά. Στο screenshot βλέπουμε ένα αποτυχημένο build για machine image του VirtualBox, διότι ο shell provisioner εκτελέστηκε πριν το σύστημα AutoYaST ολοκληρώσει την εγκατάσταση του openSUSE Leap.

Στον κόσμο του Packer κάποια SSH connections δεν πρέπει να γίνονται βιαστικά. Στο screenshot βλέπουμε ένα αποτυχημένο build για machine image του VirtualBox, διότι ο shell provisioner εκτελέστηκε πριν το σύστημα AutoYaST ολοκληρώσει την εγκατάσταση του openSUSE Leap.

Το πρώτο μας build, για το VirtualBox
Με το σκεπτικό ότι το VirtualBox αποτελεί κοινό παρονομαστή σε Linux, Mac OS X και Windows, υπό την έννοια ότι ο συγκεκριμένος desktop hypervizor διατίθεται δωρεάν και για τα τρία αυτά λειτουργικά συστήματα, ας φτιάξουμε τώρα με το Packer ένα machine image για VirtualBox. Σε ένα τερματικό, αρκεί να μεταβούμε στον κατάλογο του template και να πληκτρολογήσουμε κάτι σαν αυτό:

mbpr15:templates cvar$ PACKER_CACHE_DIR=/Users/cvar/packer_cache \
> packer build -only virtualbox leap423srv.json

Αναρωτιέστε γι’ αυτή τη μεταβλητή PACKER_CACHE_DIR; Καλά κάνετε. Η τιμή που της αναθέτουμε είναι η πλήρης διαδρομή προς έναν κατάλογο στον οποίο θέλουμε να κατεβάζει το Packer τα ISO images που χρειάζεται — αν φυσικά τα χρειάζεται. Την πρώτη φορά που θα το τρέξουμε στο σύστημά μας, θα κατεβάσει το ISO του openSUSE Leap 42.3 και θα το διατηρήσει στον κατάλογο /Users/cvar/packer_cache — ή σε αυτόν που έχουμε αναθέσει στην PACKER_CACHE_DIR, τέλος πάντων. Την επόμενη φορά που θα τρέξουμε κάποιο build στο ίδιο σύστημα, πριν αρχίσει το downloading το Packer θα ελέγξει πρώτα αν έχει το ISO που χρειάζεται στον κατάλογο της PACKER_CACHE_DIR. Αν το έχει, εννοείται πως δεν θα κατεβάσει κάτι. Τι θα γίνει όμως αν δεν έχουμε αρχικοποιήσει τη μεταβλητή PACKER_CACHE_DIR; Σ’ αυτή την περίπτωση το Packer θα θεωρήσει ότι ο κατάλογος για το κατέβασμα ISO images είναι ο τρέχων — και στην πλειονότητα των περιπτώσεων η θεώρηση αυτή μάλλον δεν θα μας βολεύει.

Παρατηρήστε εξάλλου ότι μετά την εντολή build που δώσαμε στο Packer, προσθέσαμε και την παράμετρο -only ακολουθούμενη από αυτό το virtualbox. Επειδή στο template μας (αρχείο leap423srv.json) ορίζονται τρία builds αλλά τώρα θέλουμε να τρέξουμε μόνον εκείνο για το VirtualBox, ακριβώς γι’ αυτό υποδεικνύουμε στο Packer το όνομα του builder. Αν παραλείπαμε την οδηγία -only virtualbox τότε το Packer θα επιχειρούσε να ξεκινήσει και τα τρία builds παράλληλα, όμως κάτι τέτοιο και δεν το θέλουμε και κατά θα αποτύγχανε για το template μας.

Δείτε στο ακόλουθο screenshot το output, στο τερματικό ενός συστήματος με Mac OS X, από ένα build για το VirtualBox.

Τυπική έξοδος του Packer στο τερματικό, κατά την προετοιμασία ενός machine image για το VirtualBox. Μεταξύ άλλων διακρίνεται ξεκάθαρα και η επίδραση του απλού shell provisioner, ο οποίος "φρεσκάρει" το λειτουργικό μετά την εγκατάστασή του.

Τυπική έξοδος του Packer στο τερματικό, κατά την προετοιμασία ενός machine image για το VirtualBox. Μεταξύ άλλων διακρίνεται ξεκάθαρα και η επίδραση του απλού shell provisioner, ο οποίος “φρεσκάρει” το λειτουργικό μετά την εγκατάστασή του.

Ασχέτως αν ένα Packer build είναι headless ή όχι, ανοίγοντας τον σχετικό hypervizor αποκτάμε εποπτεία της αυτοματοποιημένης διαδικασίας εγκατάστασης του guest OS. Εναλλακτικά, με τον VNC viewer της προτίμησής μας συνδεόμαστε στο localhost και στο TCP port που μας υποδεικνύει το Packer.

Ασχέτως αν ένα Packer build είναι headless ή όχι, ανοίγοντας τον σχετικό hypervizor αποκτάμε εποπτεία της αυτοματοποιημένης διαδικασίας εγκατάστασης του guest OS. Εναλλακτικά, με τον VNC viewer της προτίμησής μας συνδεόμαστε στο localhost και στο TCP port που μας υποδεικνύει το Packer.

Τι κάνουμε με τα artifacts του VirtualBox builder; Πολύ απλά, πηγαίνουμε στον κατάλογο /tmp/packer_out/oSUSE-Leap-42.3-virtualbox και εισάγουμε (import) το αρχείο osuse-leap-42.3.ovf, το οποίο περιγράφει την εικονική μηχανή.

Για την εισαγωγή (import) της νέας μηχανής στο VirtualBox, ξεκινάμε με δεξί κλικ πάνω στο αρχείο OVF. Στο pop-up του VirtualBox που εμφανίζεται, απλά κάνουμε ένα κλικ στο κουμπί Import.

Για την εισαγωγή (import) της νέας μηχανής στο VirtualBox, ξεκινάμε με δεξί κλικ πάνω στο αρχείο OVF. Στο pop-up του VirtualBox που εμφανίζεται, απλά κάνουμε ένα κλικ στο κουμπί Import.

Η εικονική μηχανή με το openSUSE Leap 42.3, την οποία εντελώς αυτοματοποιημένα κατασκεύασε το Packer, έχει πλέον εισαχθεί στο VirtualBox και είναι έτοιμη για χρήση.

Η εικονική μηχανή με το openSUSE Leap 42.3, την οποία εντελώς αυτοματοποιημένα κατασκεύασε το Packer, έχει πλέον εισαχθεί στο VirtualBox και είναι έτοιμη για χρήση.

Οι builders για VMware και QEMU
Μετά την εκτενή συζήτηση για τον builder του VirtualBox, νομίζουμε ότι δεν χρειάζονται πολλές εξηγήσεις ούτε για το γίνεται με τους άλλους δύο builders, ούτε για το πώς τους τρέχουμε. Για να είμαστε βέβαια ειλικρινείς οφείλουμε να τονίσουμε ότι υπάρχουν κάποιες διαφορές μεταξύ των builders. Αξίζει να τις δούμε.

Στον builder του VMware, λοιπόν, οι διαφορές με εκείνον του VirtualBox εντοπίζονται στα ακόλουθα ζεύγη κλειδιών/τιμών:

"guest_os_type": "opensuse-64",
"sound": false,
"usb": false,
"vnc_disable_password": true,
"tools_upload_flavor": "",

"vmx_data": {
  "memsize": "{{user `memory`}}",
  "numvcpus": "{{user `cpus`}}"
},

Ο τύπος του guest OS εξ ορισμού είναι other (βλ. κλειδί guest_os_type), ωστόσο καλό είναι να επιλέγουμε έναν τύπο πιο κοντά στο λειτουργικό σύστημα που πρόκειται να εγκατασταθεί. Προς το παρόν, ο τρόπος προκειμένου να βρίσκουμε την κατάλληλη τιμή για το κλειδί guest_os_type είναι κάπως άκομψος. Συγκεκριμένα, με έναν text editor ανοίγουμε το αρχείο .vmx μιας υπάρχουσας εικονικής μηχανής του VMware και ψάχνουμε για την τιμή που είναι αντιστοιχισμένη στην παράμετρο guestOS. Παράδειγμα:

mbpr15:~ cvar$ cd \
> Documents/Virtual Machines.localized/openSUSE Leap.vmwarevm
mbpr15:openSUSE Leap.vmwarevm cvar$ grep guestOS openSUSE Leap.vmx
guestOS = "opensuse-64"

Χάρη στις τιμές που αναθέτουμε στα κλειδιά sound και usb, ζητάμε την κατασκευή εικονικής μηχανής χωρίς κύκλωμα ήχου και χωρίς ελεγκτή USB.

Επιλέγουμε εξάλλου να μην προστατεύουμε τις συνδέσεις VNC προς την κονσόλα της εγκατάστασης με κάποιο password, γι’ αυτό και στο κλειδί vnc_disable_password αναθέτουμε την τιμή true. Αν του θέταμε την τιμή false, τότε το Packer θα παρήγαγε έναν τυχαίο κωδικό τον οποίο και θα εμφάνιζε στην έξοδό του κατά το χρόνο εκτέλεσης.

Στο δε κλειδί tools_upload_flavor θέτουμε την κενή τιμή, αφού το openSUSE Leap καταλαβαίνει ότι εγκαθίσταται σε περιβάλλον VMware και φροντίζει από μόνο του για την παρουσία των λεγόμενων WMware Tools (είναι αντίστοιχα των Guest Additions του VirtualBox).

Όσο για την ποσότητα της μνήμης RAM, καθώς και το πλήθος των πυρήνων του επεξεργαστή για το νέο VM, τα ορίζουμε μέσω του αντικειμένου που αντιστοιχίζουμε στο κλειδί vmx_data.

Η δημιουργία machine image για το VMware γίνεται με τον αναμενόμενο τρόπο. Παράδειγμα:

PACKER_CACHE_DIR=/Users/cvar/packer_cache \
> packer build -only vmware leap423srv.json

Για την εισαγωγή του machine image για VMware στον ομώνυμο hypervizor, πηγαίνουμε στον κατάλογο όπου αποθήκευσε ο builder τα artifacts, κάνουμε δεξί κλικ πάνω σε εκείνο με κατάληξη .vmx και δίνουμε Open With / VMware Fusion. Το VMware θα προτείνει να αναβαθμίσει το εικονικό hardware της μηχανής κι εμείς δεν έχουμε κάποιον λόγο να το εμποδίσουμε.

Για την εισαγωγή του machine image για VMware στον ομώνυμο hypervizor, πηγαίνουμε στον κατάλογο όπου αποθήκευσε ο builder τα artifacts, κάνουμε δεξί κλικ πάνω σε εκείνο με κατάληξη .vmx και δίνουμε Open With / VMware Fusion. Το VMware θα προτείνει να αναβαθμίσει το εικονικό hardware της μηχανής κι εμείς δεν έχουμε κάποιον λόγο να το εμποδίσουμε.

Το VM με το openSUSE Leap 42.3 έχει πλέον εισαχθεί στο VMware. Χάρη στο Packer, η κατασκευή της μηχανής έγινε εντελώς αυτοματοποιημένα.

Το VM με το openSUSE Leap 42.3 έχει πλέον εισαχθεί στο VMware. Χάρη στο Packer, η κατασκευή της μηχανής έγινε εντελώς αυτοματοποιημένα.

Ολοκληρώνουμε την εκτενή αυτή παρουσίαση του Packer με τον builder για QEMU, ονόματι libvirt. Παρατηρήστε κατ’ αρχάς την τιμή που αποδίδουμε στο κλειδί accelerator: θεωρούμε ότι οι εικονικές μηχανές του QEMU θα λειτουργούν σε διανομή Linux με πυρήνα που εμπεριέχει την τεχνολογία του Kernel-based Virtual Machine — εν συντομία KVM. Φυσικά, εκτός από τον κατάλληλο πυρήνα, στη διανομή πρέπει να είναι εγκατεστημένο και το κατάλληλο λογισμικό. Διαβάστε, π.χ., πώς φροντίζουμε γι’ αυτό σε περιβάλλον openSUSE.

Με τις τιμές που αντιστοιχίζουμε στα κλειδιά net_device και disk_interface, καθορίζουμε τους drivers που θα χρησιμοποιηθούν για το network και το disk interface αντίστοιχα. Σημειώστε ότι οι τιμές που ανατίθενται στο template μας (virtio-net και virtio αντίστοιχα) είναι οι προκαθορισμένες. Ενδιαφέρον έχει το κλειδί format και η τιμή (qcow2) που του αναθέτουμε: το artifact που θα παράξει το Packer, και θα αποτελέσει τον εικονικό δίσκο του machine image, θέλουμε να είναι ένα sparse file τύπου QCOW2.

Το δε κλειδί qemuargs θα μπορούσαμε να πούμε ότι έχει χρησιμότητα αντίστοιχη του κλειδιού vboxmanage (VirtualBox builder) ή του vmx_data (VMware builder). Στο qemuargs αντιστοιχίζουμε μία λίστα από λίστες. Κάθε υπολίστα περιλαμβάνει αλφαριθμητικά τα οποία απαρτίζουν συνδυασμούς παραμέτρων/τιμών για το qemu-system-x86_64 (ή όπως αλλιώς ονομάζεται το εκτελέσιμο του QEMU στην εκάστοτε διανομή Linux). Με την ακόλουθη αντιστοίχιση, λοιπόν…

"qemuargs": [
  [ "-m", "{{user `memory`}}M" ]
],

το Packer θα καλέσει το qemu-system-x86_64 ξεκινώντας κάπως έτσι:

qemu-system-x86 -m 2048m [...]

Αυτό σημαίνει ότι κατά την εγκατάσταση του openSUSE Leap η σχετική εικονική μηχανή θα έχει 2GiB μνήμης RAM. Προσοχή: Αν η τιμή της μεταβλητής memory είναι μικρότερη ή ίση του 1024 (1GiB μνήμης RAM), τότε το AutoYaST ενδέχεται να αποτύχει με συνέπεια να αποτύχει και το όλο build.

Προσέξτε τώρα το κλειδί boot_command, στον builder του QEMU:

"boot_command": [
  "<wait5><esc><enter><wait>",
  "linux ",
  "lang=en_US ",
  "textmode=1 ",
  "autoyast2=https://.../leap423srv.ai.libvirt.xml",
  "<enter>"
],

Αμέσως μόλις εμφανίζεται η πρώτη οθόνη του Grub, αντί το Packer να πατήσει απευθείας το [ESC] (<esc>), μετά το [Enter] (<enter>) και ύστερα να περιμένει ένα δευτερόλεπτο (<wait>) πριν αρχίσει την πληκτρολόγηση παραμέτρων εκκίνησης, του ζητάμε να περιμένει για πέντε δευτερόλεπτα (<wait5>). Αυτό το κάνουμε ως workaround: Υπήρχαν περιπτώσεις κατά τις οποίες το Packer αποτύγχανε να στείλει, μέσω VNC, στην κονσόλα εγκατάστασης τις απαραίτητες ακολουθίες keystrokes. Δείτε εξάλλου ότι το AutoYaST profile για την εγκατάσταση σε περιβάλλον QEMU δεν είναι εκείνο που χρησιμοποιήσαμε στους builders για VirtualBox και VMware. Όπως αναφέραμε και προηγουμένως, ο λόγος έχει να κάνει με τις διαφορετικές ονομασίες των συσκευών για τα block devices.

Τέλος, προκειμένου να τρέξουμε τον builder για QEMU, στην κονσόλα της αγαπημένης μας διανομής (όλοι ξέρουμε ποια είναι αυτή :)) πληκτρολογούμε κάτι τέτοιο:

PACKER_CACHE_DIR=/home/userson/packer_cache \
> packer build -only libvirt leap423srv.json

Αυτοματοποιημένη δημιουργία machine image για το QEMU, χάρη στο Packer. (Ναι, τα builds που τρέχουμε κάποιες φορές δεν είναι headless.)

Αυτοματοποιημένη δημιουργία machine image για το QEMU, χάρη στο Packer. (Ναι, τα builds που τρέχουμε κάποιες φορές δεν είναι headless.)

Leave a Reply

You must be logged in to post a comment.

Σύνδεση

Αρχείο δημοσιεύσεων