Εισαγωγή στη Μαριονέτα

Το Puppet είναι ένα σύστημα διαχείρισης παραμέτρων. Χρησιμοποιείται για να φέρει τους κεντρικούς υπολογιστές στην επιθυμητή κατάσταση και να διατηρήσει αυτήν την κατάσταση.

Δουλεύω με την Puppet για πάνω από πέντε χρόνια τώρα. Αυτό το κείμενο είναι ουσιαστικά μια μεταφρασμένη και αναδιαλεγμένη συλλογή βασικών σημείων από την επίσημη τεκμηρίωση, η οποία θα επιτρέψει στους αρχάριους να κατανοήσουν γρήγορα την ουσία του Puppet.

Εισαγωγή στη Μαριονέτα

Βασικές πληροφορίες

Το λειτουργικό σύστημα της Puppet είναι πελάτης-διακομιστής, αν και υποστηρίζει επίσης λειτουργία χωρίς διακομιστή με περιορισμένη λειτουργικότητα.

Χρησιμοποιείται ένα μοντέλο λειτουργίας έλξης: από προεπιλογή, μία φορά κάθε μισή ώρα, οι πελάτες επικοινωνούν με τον διακομιστή για μια διαμόρφωση και την εφαρμόζουν. Εάν έχετε εργαστεί με το Ansible, τότε χρησιμοποιούν ένα διαφορετικό μοντέλο push: ο διαχειριστής ξεκινά τη διαδικασία εφαρμογής της διαμόρφωσης, οι ίδιοι οι πελάτες δεν θα εφαρμόσουν τίποτα.

Κατά τη διάρκεια της επικοινωνίας δικτύου, χρησιμοποιείται αμφίδρομη κρυπτογράφηση TLS: ο διακομιστής και ο πελάτης έχουν τα δικά τους ιδιωτικά κλειδιά και τα αντίστοιχα πιστοποιητικά. Συνήθως ο διακομιστής εκδίδει πιστοποιητικά για πελάτες, αλλά καταρχήν είναι δυνατή η χρήση μιας εξωτερικής ΑΠ.

Εισαγωγή στα μανιφέστα

Στην ορολογία Puppet στον διακομιστή μαριονέτας συνδέω-συωδεομαι κόμβους (κόμβοι). Η διαμόρφωση για τους κόμβους είναι γραμμένη σε μανιφέστα σε ειδική γλώσσα προγραμματισμού - Puppet DSL.

Το Puppet DSL είναι μια δηλωτική γλώσσα. Περιγράφει την επιθυμητή κατάσταση του κόμβου με τη μορφή δηλώσεων μεμονωμένων πόρων, για παράδειγμα:

  • Το αρχείο υπάρχει και έχει συγκεκριμένο περιεχόμενο.
  • Το πακέτο έχει εγκατασταθεί.
  • Η υπηρεσία ξεκίνησε.

Οι πόροι μπορούν να διασυνδεθούν:

  • Υπάρχουν εξαρτήσεις, επηρεάζουν τη σειρά με την οποία χρησιμοποιούνται οι πόροι.
    Για παράδειγμα, «πρώτα εγκαταστήστε το πακέτο, μετά επεξεργαστείτε το αρχείο διαμόρφωσης και μετά ξεκινήστε την υπηρεσία».
  • Υπάρχουν ειδοποιήσεις - εάν ένας πόρος έχει αλλάξει, στέλνει ειδοποιήσεις στους πόρους που είναι εγγεγραμμένοι σε αυτόν.
    Για παράδειγμα, εάν αλλάξει το αρχείο διαμόρφωσης, μπορείτε να επανεκκινήσετε αυτόματα την υπηρεσία.

Επιπλέον, το Puppet DSL έχει συναρτήσεις και μεταβλητές, καθώς και προτάσεις υπό όρους και επιλογείς. Υποστηρίζονται επίσης διάφοροι μηχανισμοί προτύπων - EPP και ERB.

Το Puppet είναι γραμμένο σε Ruby, έτσι πολλές από τις κατασκευές και τους όρους λαμβάνονται από εκεί. Το Ruby σάς επιτρέπει να επεκτείνετε το Puppet - προσθέστε σύνθετη λογική, νέους τύπους πόρων, λειτουργίες.

Ενώ εκτελείται το Puppet, οι δηλώσεις για κάθε συγκεκριμένο κόμβο στο διακομιστή μεταγλωττίζονται σε έναν κατάλογο. Κατάλογος είναι μια λίστα πόρων και των σχέσεών τους μετά τον υπολογισμό της τιμής των συναρτήσεων, των μεταβλητών και της επέκτασης των εντολών υπό όρους.

Σύνταξη και στυλ κώδικα

Ακολουθούν ενότητες της επίσημης τεκμηρίωσης που θα σας βοηθήσουν να κατανοήσετε τη σύνταξη εάν τα παραδείγματα που παρέχονται δεν είναι αρκετά:

Ακολουθεί ένα παράδειγμα για το πώς φαίνεται το μανιφέστο:

# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
  # Конфигурация по сути является перечислением ресурсов и их параметров.
  #
  # У каждого ресурса есть тип и название.
  #
  # Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
  #
  # Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
  # Про разные типы ресурсов написано ниже.
  #
  # После типа в фигурных скобках пишется название ресурса, потом двоеточие,
  # дальше идёт опциональное перечисление параметров ресурса и их значений.
  # Значения параметров указываются через т.н. hash rocket (=>).
  resource { 'title':
    param1 => value1,
    param2 => value2,
    param3 => value3,
  }
}

Η εσοχή και οι αλλαγές γραμμής δεν αποτελούν υποχρεωτικό μέρος της δήλωσης, αλλά υπάρχει μια προτεινόμενη οδηγός στυλ. Περίληψη:

  • Δεν χρησιμοποιούνται εσοχές δύο διαστημάτων, καρτέλες.
  • Τα σγουρά σιδεράκια χωρίζονται με ένα κενό, τα άνω και κάτω τελεία δεν χωρίζονται με ένα διάστημα.
  • Κόμματα μετά από κάθε παράμετρο, συμπεριλαμβανομένης της τελευταίας. Κάθε παράμετρος βρίσκεται σε ξεχωριστή γραμμή. Γίνεται εξαίρεση για την περίπτωση χωρίς παραμέτρους και μία παράμετρο: μπορείτε να γράψετε σε μία γραμμή και χωρίς κόμμα (δηλ. resource { 'title': } и resource { 'title': param => value }).
  • Τα βέλη στις παραμέτρους πρέπει να είναι στο ίδιο επίπεδο.
  • Μπροστά τους είναι γραμμένα βέλη σχέσης πόρων.

Τοποθεσία αρχείων στον pappetserver

Για περαιτέρω εξήγηση, θα εισαγάγω την έννοια του "root directory". Ο ριζικός κατάλογος είναι ο κατάλογος που περιέχει τη διαμόρφωση Puppet για έναν συγκεκριμένο κόμβο.

Ο ριζικός κατάλογος ποικίλλει ανάλογα με την έκδοση του Puppet και τα περιβάλλοντα που χρησιμοποιούνται. Τα περιβάλλοντα είναι ανεξάρτητα σύνολα ρυθμίσεων που αποθηκεύονται σε ξεχωριστούς καταλόγους. Συνήθως χρησιμοποιείται σε συνδυασμό με το git, οπότε τα περιβάλλοντα δημιουργούνται από κλάδους git. Αντίστοιχα, κάθε κόμβος βρίσκεται σε ένα ή άλλο περιβάλλον. Αυτό μπορεί να ρυθμιστεί στον ίδιο τον κόμβο ή στο ENC, για το οποίο θα μιλήσω στο επόμενο άρθρο.

  • Στην τρίτη έκδοση ("παλιά Μαριονέτα") ο βασικός κατάλογος ήταν /etc/puppet. Η χρήση περιβαλλόντων είναι προαιρετική - για παράδειγμα, δεν τα χρησιμοποιούμε με το παλιό Puppet. Εάν χρησιμοποιούνται περιβάλλοντα, συνήθως αποθηκεύονται σε /etc/puppet/environments, ο ριζικός κατάλογος θα είναι ο κατάλογος περιβάλλοντος. Εάν δεν χρησιμοποιούνται περιβάλλοντα, ο ριζικός κατάλογος θα είναι ο βασικός κατάλογος.
  • Ξεκινώντας από την τέταρτη έκδοση ("new Puppet"), η χρήση περιβαλλόντων έγινε υποχρεωτική και ο βασικός κατάλογος μετακινήθηκε στο /etc/puppetlabs/code. Αντίστοιχα, τα περιβάλλοντα αποθηκεύονται σε /etc/puppetlabs/code/environments, ο ριζικός κατάλογος είναι ο κατάλογος περιβάλλοντος.

Πρέπει να υπάρχει ένας υποκατάλογος στον ριζικό κατάλογο manifests, το οποίο περιέχει ένα ή περισσότερα μανιφέστα που περιγράφουν τους κόμβους. Επιπλέον, θα πρέπει να υπάρχει ένας υποκατάλογος modules, το οποίο περιέχει τις ενότητες. Θα σας πω ποιες είναι οι ενότητες λίγο αργότερα. Επιπλέον, το παλιό Puppet μπορεί επίσης να έχει έναν υποκατάλογο files, το οποίο περιέχει διάφορα αρχεία που αντιγράφουμε στους κόμβους. Στο νέο Puppet, όλα τα αρχεία τοποθετούνται σε modules.

Τα αρχεία Manifest έχουν την επέκταση .pp.

Μερικά παραδείγματα μάχης

Περιγραφή του κόμβου και του πόρου σε αυτόν

Στον κόμβο server1.testdomain πρέπει να δημιουργηθεί ένα αρχείο /etc/issue με περιεχόμενο Debian GNU/Linux n l. Το αρχείο πρέπει να ανήκει σε χρήστη και ομάδα root, τα δικαιώματα πρόσβασης πρέπει να είναι 644.

Γράφουμε ένα μανιφέστο:

node 'server1.testdomain' {   # блок конфигурации, относящийся к ноде server1.testdomain
    file { '/etc/issue':   # описываем файл /etc/issue
        ensure  => present,   # этот файл должен существовать
        content => 'Debian GNU/Linux n l',   # у него должно быть такое содержимое
        owner   => root,   # пользователь-владелец
        group   => root,   # группа-владелец
        mode    => '0644',   # права на файл. Они заданы в виде строки (в кавычках), потому что иначе число с 0 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
    }
}

Σχέσεις μεταξύ πόρων σε έναν κόμβο

Στον κόμβο server2.testdomain Το nginx πρέπει να εκτελείται, δουλεύοντας με μια προηγουμένως προετοιμασμένη διαμόρφωση.

Ας αναλύσουμε το πρόβλημα:

  • Το πακέτο πρέπει να εγκατασταθεί nginx.
  • Είναι απαραίτητο τα αρχεία διαμόρφωσης να αντιγραφούν από τον διακομιστή.
  • Η υπηρεσία πρέπει να λειτουργεί nginx.
  • Εάν η διαμόρφωση ενημερωθεί, η υπηρεσία πρέπει να επανεκκινηθεί.

Γράφουμε ένα μανιφέστο:

node 'server2.testdomain' {   # блок конфигурации, относящийся к ноде server2.testdomain
    package { 'nginx':   # описываем пакет nginx
        ensure => installed,   # он должен быть установлен
    }
  # Прямая стрелка (->) говорит о том, что ресурс ниже должен
  # создаваться после ресурса, описанного выше.
  # Такие зависимости транзитивны.
    -> file { '/etc/nginx':   # описываем файл /etc/nginx
        ensure  => directory,   # это должна быть директория
        source  => 'puppet:///modules/example/nginx-conf',   # её содержимое нужно брать с паппет-сервера по указанному адресу
        recurse => true,   # копировать файлы рекурсивно
        purge   => true,   # нужно удалять лишние файлы (те, которых нет в источнике)
        force   => true,   # удалять лишние директории
    }
  # Волнистая стрелка (~>) говорит о том, что ресурс ниже должен
  # подписаться на изменения ресурса, описанного выше.
  # Волнистая стрелка включает в себя прямую (->).
    ~> service { 'nginx':   # описываем сервис nginx
        ensure => running,   # он должен быть запущен
        enable => true,   # его нужно запускать автоматически при старте системы
    }
  # Когда ресурс типа service получает уведомление,
  # соответствующий сервис перезапускается.
}

Για να λειτουργήσει αυτό, χρειάζεστε περίπου την ακόλουθη θέση αρχείου στον διακομιστή μαριονέτας:

/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│   └── site.pp
└── modules/
    └── example/
        └── files/
            └── nginx-conf/
                ├── nginx.conf
                ├── mime.types
                └── conf.d/
                    └── some.conf

Τύποι πόρων

Μια πλήρης λίστα των υποστηριζόμενων τύπων πόρων μπορείτε να βρείτε εδώ στην τεκμηρίωση, εδώ θα περιγράψω πέντε βασικούς τύπους, που στην πρακτική μου αρκούν για να λύσουν τα περισσότερα προβλήματα.

φιλέτο

Διαχειρίζεται αρχεία, καταλόγους, συμβολικούς συνδέσμους, τα περιεχόμενά τους και δικαιώματα πρόσβασης.

Παράμετροι:

  • όνομα πόρου — διαδρομή προς το αρχείο (προαιρετικό)
  • μονοπάτι — διαδρομή προς το αρχείο (αν δεν προσδιορίζεται στο όνομα)
  • εξασφαλίζω - Τύπος αρχείου:
    • absent - διαγράψτε ένα αρχείο
    • present — πρέπει να υπάρχει αρχείο οποιουδήποτε τύπου (αν δεν υπάρχει αρχείο, θα δημιουργηθεί ένα κανονικό αρχείο)
    • file - κανονικό αρχείο
    • directory - Ευρετήριο
    • link - συμβολικός σύνδεσμος
  • περιεχόμενο — περιεχόμενα αρχείου (κατάλληλο μόνο για κανονικά αρχεία, δεν μπορεί να χρησιμοποιηθεί μαζί με πηγή ή στόχος)
  • πηγή — ένας σύνδεσμος προς τη διαδρομή από την οποία θέλετε να αντιγράψετε τα περιεχόμενα του αρχείου (δεν μπορεί να χρησιμοποιηθεί μαζί με περιεχόμενο ή στόχος). Μπορεί να οριστεί είτε ως URI με σχήμα puppet: (τότε θα χρησιμοποιηθούν αρχεία από τον διακομιστή μαριονέτας) και με το σχήμα http: (Ελπίζω να είναι ξεκάθαρο τι θα συμβεί σε αυτή την περίπτωση), και μάλιστα με το διάγραμμα file: ή ως απόλυτη διαδρομή χωρίς σχήμα (τότε θα χρησιμοποιηθεί το αρχείο από το τοπικό FS στον κόμβο)
  • στόχος — πού πρέπει να δείχνει ο συμβολικός σύνδεσμος (δεν μπορεί να χρησιμοποιηθεί μαζί με περιεχόμενο ή πηγή)
  • ιδιοκτήτης — ο χρήστης που θα πρέπει να είναι κάτοχος του αρχείου
  • ομάδα — η ομάδα στην οποία πρέπει να ανήκει το αρχείο
  • τρόπος — δικαιώματα αρχείου (ως συμβολοσειρά)
  • επαναλαμβάνω - επιτρέπει την αναδρομική επεξεργασία καταλόγου
  • εκκαθάριση - επιτρέπει τη διαγραφή αρχείων που δεν περιγράφονται στο Puppet
  • δύναμης - επιτρέπει τη διαγραφή καταλόγων που δεν περιγράφονται στο Puppet

πακέτο

Εγκαθιστά και αφαιρεί πακέτα. Δυνατότητα χειρισμού ειδοποιήσεων - επανεγκαθιστά το πακέτο εάν έχει καθοριστεί η παράμετρος reinstall_on_refresh.

Παράμετροι:

  • όνομα πόρου — όνομα πακέτου (προαιρετικό)
  • όνομα — όνομα πακέτου (αν δεν προσδιορίζεται στο όνομα)
  • προμηθευτής — διαχειριστής πακέτων προς χρήση
  • εξασφαλίζω — επιθυμητή κατάσταση της συσκευασίας:
    • present, installed - οποιαδήποτε έκδοση έχει εγκατασταθεί
    • latest - Εγκαταστάθηκε η τελευταία έκδοση
    • absent - διαγράφηκε (apt-get remove)
    • purged — διαγράφηκε μαζί με αρχεία διαμόρφωσης (apt-get purge)
    • held - η έκδοση πακέτου είναι κλειδωμένη (apt-mark hold)
    • любая другая строка — έχει εγκατασταθεί η καθορισμένη έκδοση
  • reinstall_on_refresh - αν true, τότε με τη λήψη της ειδοποίησης το πακέτο θα επανεγκατασταθεί. Χρήσιμο για διανομές που βασίζονται σε πηγές, όπου η αναδόμηση πακέτων μπορεί να είναι απαραίτητη κατά την αλλαγή των παραμέτρων κατασκευής. Προκαθορισμένο false.

υπηρεσία

Διαχειρίζεται υπηρεσίες. Δυνατότητα επεξεργασίας ειδοποιήσεων - επανεκκινεί την υπηρεσία.

Παράμετροι:

  • όνομα πόρου — υπηρεσία προς διαχείριση (προαιρετικό)
  • όνομα — την υπηρεσία που πρέπει να διαχειρίζεται (αν δεν προσδιορίζεται στο όνομα)
  • εξασφαλίζω — επιθυμητή κατάσταση της υπηρεσίας:
    • running - εκτοξεύτηκε
    • stopped - σταμάτησε
  • ενεργοποιήσετε — ελέγχει τη δυνατότητα έναρξης της υπηρεσίας:
    • true — το autorun είναι ενεργοποιημένο (systemctl enable)
    • mask - μεταμφιεσμένοι (systemctl mask)
    • false — η αυτόματη εκτέλεση είναι απενεργοποιημένη (systemctl disable)
  • επανεκκίνηση - εντολή για επανεκκίνηση της υπηρεσίας
  • κατάσταση — εντολή για έλεγχο της κατάστασης υπηρεσίας
  • έχει επανεκκίνηση — υποδεικνύετε εάν το initscript υπηρεσίας υποστηρίζει επανεκκίνηση. Αν false και καθορίζεται η παράμετρος επανεκκίνηση — χρησιμοποιείται η τιμή αυτής της παραμέτρου. Αν false και παράμετρος επανεκκίνηση δεν καθορίζεται - η υπηρεσία σταμάτησε και άρχισε να επανεκκινείται (αλλά το systemd χρησιμοποιεί την εντολή systemctl restart).
  • hasstatus — υποδεικνύετε εάν το initscript υπηρεσίας υποστηρίζει την εντολή status. Αν false, τότε χρησιμοποιείται η τιμή της παραμέτρου κατάσταση. Προκαθορισμένο true.

exec

Εκτελεί εξωτερικές εντολές. Εάν δεν καθορίσετε παραμέτρους δημιουργεί, μόνο εάν, εκτός ή ανανεωμένα, η εντολή θα εκτελείται κάθε φορά που εκτελείται το Puppet. Δυνατότητα επεξεργασίας ειδοποιήσεων - εκτελεί μια εντολή.

Παράμετροι:

  • όνομα πόρου — εντολή προς εκτέλεση (προαιρετικό)
  • εντολή — την εντολή που πρέπει να εκτελεστεί (αν δεν προσδιορίζεται στο όνομα)
  • μονοπάτι — διαδρομές στις οποίες μπορείτε να αναζητήσετε το εκτελέσιμο αρχείο
  • μόνο εάν — εάν η εντολή που καθορίζεται σε αυτήν την παράμετρο συμπληρωθεί με έναν κωδικό μηδενικής επιστροφής, η κύρια εντολή θα εκτελεστεί
  • εκτός — εάν η εντολή που καθορίζεται σε αυτήν την παράμετρο συμπληρωθεί με έναν κωδικό επιστροφής μη μηδενικού, θα εκτελεστεί η κύρια εντολή
  • δημιουργεί — εάν το αρχείο που καθορίζεται σε αυτήν την παράμετρο δεν υπάρχει, θα εκτελεστεί η κύρια εντολή
  • ανανεωμένα - αν true, τότε η εντολή θα εκτελεστεί μόνο όταν αυτό το exec λάβει ειδοποίηση από άλλους πόρους
  • cwd — κατάλογος από τον οποίο εκτελείται η εντολή
  • χρήστη — ο χρήστης από τον οποίο θα εκτελεστεί η εντολή
  • προμηθευτής - πώς να εκτελέσετε την εντολή:
    • posix — δημιουργείται απλώς μια θυγατρική διαδικασία, βεβαιωθείτε ότι έχετε καθορίσει μονοπάτι
    • κέλυφος - η εντολή εκκινείται στο κέλυφος /bin/sh, ενδέχεται να μην προσδιορίζεται μονοπάτι, μπορείτε να χρησιμοποιήσετε globbing, σωλήνες και άλλα χαρακτηριστικά κελύφους. Συνήθως εντοπίζεται αυτόματα εάν υπάρχουν ειδικοί χαρακτήρες (|, ;, &&, || και ούτω καθεξής).

cron

Ελέγχει τα cronjobs.

Παράμετροι:

  • όνομα πόρου - απλώς κάποιου είδους αναγνωριστικό
  • εξασφαλίζω — πολιτεία του στέμματος:
    • present - δημιουργία αν δεν υπάρχει
    • absent - διαγράψτε εάν υπάρχει
  • εντολή - ποια εντολή να τρέξει
  • θετική ατμόσφαιρα — σε ποιο περιβάλλον να εκτελεστεί η εντολή (λίστα μεταβλητών περιβάλλοντος και οι τιμές τους μέσω =)
  • χρήστη — από ποιον χρήστη να εκτελέσετε την εντολή
  • λεπτό, ώρα, καθημερινή, μήνας, μήνας — πότε να τρέξεις cron. Εάν κάποιο από αυτά τα χαρακτηριστικά δεν καθορίζεται, η τιμή του στο crontab θα είναι *.

Στο Puppet 6.0 cron σαν αφαιρεθεί από το κουτί στον puppetserver, επομένως δεν υπάρχει τεκμηρίωση στον γενικό ιστότοπο. Αλλά αυτός είναι στο κουτί σε puppet-agent, οπότε δεν χρειάζεται να το εγκαταστήσετε ξεχωριστά. Μπορείτε να δείτε την τεκμηρίωση για αυτό στην τεκμηρίωση για την πέμπτη έκδοση του PuppetΉ στο GitHub.

Γενικά για τους πόρους

Απαιτήσεις για μοναδικότητα πόρων

Το πιο συνηθισμένο λάθος που συναντάμε είναι Διπλότυπη δήλωση. Αυτό το σφάλμα παρουσιάζεται όταν δύο ή περισσότεροι πόροι του ίδιου τύπου με το ίδιο όνομα εμφανίζονται στον κατάλογο.

Γι' αυτό θα ξαναγράψω: Οι δηλώσεις για τον ίδιο κόμβο δεν πρέπει να περιέχουν πόρους του ίδιου τύπου με τον ίδιο τίτλο!

Μερικές φορές υπάρχει ανάγκη εγκατάστασης πακέτων με το ίδιο όνομα, αλλά με διαφορετικούς διαχειριστές πακέτων. Σε αυτήν την περίπτωση, πρέπει να χρησιμοποιήσετε την παράμετρο nameγια να αποφύγετε το σφάλμα:

package { 'ruby-mysql':
  ensure   => installed,
  name     => 'mysql',
  provider => 'gem',
}
package { 'python-mysql':
  ensure   => installed,
  name     => 'mysql',
  provider => 'pip',
}

Άλλοι τύποι πόρων έχουν παρόμοιες επιλογές για την αποφυγή διπλασιασμού − name у υπηρεσία, command у exec, και ούτω καθεξής.

Μεταπαράμετροι

Κάθε τύπος πόρου έχει κάποιες ειδικές παραμέτρους, ανεξάρτητα από τη φύση του.

Πλήρης λίστα μετα-παραμέτρων στην τεκμηρίωση Puppet.

Σύντομη λίστα:

  • απαιτούν — αυτή η παράμετρος υποδεικνύει από ποιους πόρους εξαρτάται αυτός ο πόρος.
  • πριν - Αυτή η παράμετρος καθορίζει ποιοι πόροι εξαρτώνται από αυτόν τον πόρο.
  • συνεισφέρω — αυτή η παράμετρος καθορίζει από ποιους πόρους λαμβάνει ειδοποιήσεις αυτός ο πόρος.
  • κοινοποιούν — Αυτή η παράμετρος καθορίζει ποιοι πόροι λαμβάνουν ειδοποιήσεις από αυτόν τον πόρο.

Όλες οι αναφερόμενες μεταπαράμετροι δέχονται είτε έναν σύνδεσμο πόρου είτε έναν πίνακα συνδέσμων σε αγκύλες.

Σύνδεσμοι με πόρους

Ένας σύνδεσμος πόρων είναι απλώς μια αναφορά του πόρου. Χρησιμοποιούνται κυρίως για να υποδείξουν εξαρτήσεις. Η αναφορά σε έναν ανύπαρκτο πόρο θα προκαλέσει σφάλμα μεταγλώττισης.

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

Παράδειγμα:

file { '/file1': ensure => present }
file { '/file2':
  ensure => directory,
  before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']

Εξαρτήσεις και ειδοποιήσεις

Τεκμηρίωση εδώ.

Όπως αναφέρθηκε προηγουμένως, οι απλές εξαρτήσεις μεταξύ των πόρων είναι μεταβατικές. Παρεμπιπτόντως, να είστε προσεκτικοί όταν προσθέτετε εξαρτήσεις - μπορείτε να δημιουργήσετε κυκλικές εξαρτήσεις, οι οποίες θα προκαλέσουν σφάλμα μεταγλώττισης.

Σε αντίθεση με τις εξαρτήσεις, οι ειδοποιήσεις δεν είναι μεταβατικές. Για τις ειδοποιήσεις ισχύουν οι ακόλουθοι κανόνες:

  • Εάν ο πόρος λάβει μια ειδοποίηση, ενημερώνεται. Οι ενέργειες ενημέρωσης εξαρτώνται από τον τύπο του πόρου − exec εκτελεί την εντολή, υπηρεσία επανεκκινεί την υπηρεσία, πακέτο επανατοποθετεί το πακέτο. Εάν ο πόρος δεν έχει ορίσει μια ενέργεια ενημέρωσης, τότε δεν συμβαίνει τίποτα.
  • Κατά τη διάρκεια μιας εκτέλεσης του Puppet, ο πόρος ενημερώνεται όχι περισσότερες από μία φορές. Αυτό είναι δυνατό επειδή οι ειδοποιήσεις περιλαμβάνουν εξαρτήσεις και το γράφημα εξαρτήσεων δεν περιέχει κύκλους.
  • Εάν το Puppet αλλάξει την κατάσταση ενός πόρου, ο πόρος στέλνει ειδοποιήσεις σε όλους τους πόρους που είναι εγγεγραμμένοι σε αυτόν.
  • Εάν ένας πόρος ενημερωθεί, στέλνει ειδοποιήσεις σε όλους τους πόρους που είναι εγγεγραμμένοι σε αυτόν.

Χειρισμός απροσδιόριστων παραμέτρων

Κατά κανόνα, εάν κάποια παράμετρος πόρου δεν έχει προεπιλεγμένη τιμή και αυτή η παράμετρος δεν καθορίζεται στο μανιφέστο, τότε το Puppet δεν θα αλλάξει αυτήν την ιδιότητα για τον αντίστοιχο πόρο στον κόμβο. Για παράδειγμα, εάν ένας πόρος του τύπου φιλέτο δεν προσδιορίζεται παράμετρος owner, τότε το Puppet δεν θα αλλάξει τον κάτοχο του αντίστοιχου αρχείου.

Εισαγωγή σε κλάσεις, μεταβλητές και ορισμούς

Ας υποθέσουμε ότι έχουμε πολλούς κόμβους που έχουν το ίδιο τμήμα της διαμόρφωσης, αλλά υπάρχουν και διαφορές - διαφορετικά θα μπορούσαμε να τα περιγράψουμε όλα σε ένα μπλοκ node {}. Φυσικά, μπορείτε απλά να αντιγράψετε πανομοιότυπα μέρη της διαμόρφωσης, αλλά γενικά αυτή είναι μια κακή λύση - η διαμόρφωση μεγαλώνει και αν αλλάξετε το γενικό μέρος της διαμόρφωσης, θα πρέπει να επεξεργαστείτε το ίδιο πράγμα σε πολλά σημεία. Ταυτόχρονα, είναι εύκολο να κάνεις λάθος και γενικά, η αρχή DRY (μην επαναλαμβάνεις τον εαυτό σου) επινοήθηκε για κάποιο λόγο.

Για την επίλυση αυτού του προβλήματος υπάρχει ένα τέτοιο σχέδιο όπως κατηγορία.

Μαθήματα

Κατηγορία είναι ένα επώνυμο μπλοκ κώδικα poppet. Απαιτούνται τάξεις για την επαναχρησιμοποίηση του κώδικα.

Πρώτα πρέπει να περιγραφεί η τάξη. Η ίδια η περιγραφή δεν προσθέτει πόρους πουθενά. Η τάξη περιγράφεται σε δηλώσεις:

# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
    ...
}

Μετά από αυτό η τάξη μπορεί να χρησιμοποιηθεί:

# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше

Ένα παράδειγμα από την προηγούμενη εργασία - ας μετακινήσουμε την εγκατάσταση και τη διαμόρφωση του nginx σε μια κλάση:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => 'puppet:///modules/example/nginx-conf',
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    include nginx_example
}

Μεταβλητές

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

Μπορεί να γίνει χρησιμοποιώντας μεταβλητές.

Προσοχή: οι μεταβλητές στο Puppet είναι αμετάβλητες!

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

Παράδειγμα εργασίας με μεταβλητές:

# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"

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

Παραδείγματα χώρου ονομάτων:

  • καθολική - μεταβλητές εκτός της περιγραφής κλάσης ή κόμβου πηγαίνουν εκεί.
  • Χώρος ονομάτων κόμβου στην περιγραφή κόμβου.
  • χώρος ονομάτων κλάσης στην περιγραφή κλάσης.

Για να αποφύγετε την ασάφεια κατά την πρόσβαση σε μια μεταβλητή, μπορείτε να καθορίσετε τον χώρο ονομάτων στο όνομα της μεταβλητής:

# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var

Ας συμφωνήσουμε ότι η διαδρομή προς τη διαμόρφωση του nginx βρίσκεται στη μεταβλητή $nginx_conf_source. Τότε η τάξη θα μοιάζει με αυτό:

class nginx_example {
    package { 'nginx':
        ensure => installed,
    }
    -> file { '/etc/nginx':
        ensure => directory,
        source => $nginx_conf_source,   # здесь используем переменную вместо фиксированной строки
        recure => true,
        purge  => true,
        force  => true,
    }
    ~> service { 'nginx':
        ensure => running,
        enable => true,
    }
}

node 'server2.testdomain' {
    $nginx_conf_source = 'puppet:///modules/example/nginx-conf'
    include nginx_example
}

Ωστόσο, το παράδειγμα που δίνεται είναι κακό, επειδή υπάρχει κάποια «μυστική γνώση» ότι κάπου μέσα στην τάξη χρησιμοποιείται μια μεταβλητή με τέτοιο όνομα. Είναι πολύ πιο σωστό να γίνει γενική αυτή η γνώση - οι τάξεις μπορούν να έχουν παραμέτρους.

Παράμετροι κλάσης είναι μεταβλητές στον χώρο ονομάτων της κλάσης, καθορίζονται στην κεφαλίδα της κλάσης και μπορούν να χρησιμοποιηθούν όπως οι κανονικές μεταβλητές στο σώμα της κλάσης. Οι τιμές των παραμέτρων καθορίζονται όταν χρησιμοποιείται η κλάση στο μανιφέστο.

Η παράμετρος μπορεί να οριστεί σε μια προεπιλεγμένη τιμή. Εάν μια παράμετρος δεν έχει προεπιλεγμένη τιμή και η τιμή δεν οριστεί όταν χρησιμοποιείται, θα προκαλέσει σφάλμα μεταγλώττισης.

Ας παραμετροποιήσουμε την κλάση από το παραπάνω παράδειγμα και ας προσθέσουμε δύο παραμέτρους: η πρώτη, απαιτείται, είναι η διαδρομή προς τη διαμόρφωση και η δεύτερη, προαιρετική, είναι το όνομα του πακέτου με nginx (στο Debian, για παράδειγμα, υπάρχουν πακέτα nginx, nginx-light, nginx-full).

# переменные описываются сразу после имени класса в круглых скобках
class nginx_example (
  $conf_source,
  $package_name = 'nginx-light', # параметр со значением по умолчанию
) {
  package { $package_name:
    ensure => installed,
  }
  -> file { '/etc/nginx':
    ensure  => directory,
    source  => $conf_source,
    recurse => true,
    purge   => true,
    force   => true,
  }
  ~> service { 'nginx':
    ensure => running,
    enable => true,
  }
}

node 'server2.testdomain' {
  # если мы хотим задать параметры класса, функция include не подойдёт* — нужно использовать resource-style declaration
  # *на самом деле подойдёт, но про это расскажу в следующей серии. Ключевое слово "Hiera".
  class { 'nginx_example':
    conf_source => 'puppet:///modules/example/nginx-conf',   # задаём параметры класса точно так же, как параметры для других ресурсов
  }
}

Στο Puppet, οι μεταβλητές πληκτρολογούνται. Τρώω πολλούς τύπους δεδομένων. Οι τύποι δεδομένων χρησιμοποιούνται συνήθως για την επικύρωση τιμών παραμέτρων που μεταβιβάζονται σε κλάσεις και ορισμούς. Εάν η παράμετρος που πέρασε δεν ταιριάζει με τον καθορισμένο τύπο, θα προκύψει σφάλμα μεταγλώττισης.

Ο τύπος γράφεται αμέσως πριν από το όνομα της παραμέτρου:

class example (
  String $param1,
  Integer $param2,
  Array $param3,
  Hash $param4,
  Hash[String, String] $param5,
) {
  ...
}

Τάξεις: περιλαμβάνει classname vs class{'classname':}

Κάθε τάξη είναι ένας πόρος του τύπου τάξη. Όπως με κάθε άλλο τύπο πόρου, δεν μπορούν να υπάρχουν δύο περιπτώσεις της ίδιας κλάσης στον ίδιο κόμβο.

Εάν προσπαθήσετε να προσθέσετε μια κλάση στον ίδιο κόμβο δύο φορές χρησιμοποιώντας class { 'classname':} (καμία διαφορά, με διαφορετικές ή πανομοιότυπες παραμέτρους), θα υπάρξει σφάλμα μεταγλώττισης. Αλλά αν χρησιμοποιείτε μια κλάση στο στυλ πόρων, μπορείτε αμέσως να ορίσετε ρητά όλες τις παραμέτρους της στο μανιφέστο.

Ωστόσο, εάν χρησιμοποιείτε include, τότε η κλάση μπορεί να προστεθεί όσες φορές επιθυμείτε. Γεγονός είναι ότι include είναι μια ανεπαρκής συνάρτηση που ελέγχει εάν μια κλάση έχει προστεθεί στον κατάλογο. Εάν η κλάση δεν βρίσκεται στον κατάλογο, την προσθέτει και αν υπάρχει ήδη, δεν κάνει τίποτα. Σε περίπτωση όμως χρήσης include Δεν μπορείτε να ορίσετε παραμέτρους κλάσης κατά τη δήλωση κλάσης - όλες οι απαιτούμενες παράμετροι πρέπει να οριστούν σε μια εξωτερική πηγή δεδομένων - Hiera ή ENC. Θα μιλήσουμε για αυτούς στο επόμενο άρθρο.

Ορίζει

Όπως ειπώθηκε στο προηγούμενο μπλοκ, η ίδια κλάση δεν μπορεί να είναι παρούσα σε έναν κόμβο περισσότερες από μία φορές. Ωστόσο, σε ορισμένες περιπτώσεις πρέπει να μπορείτε να χρησιμοποιήσετε το ίδιο μπλοκ κώδικα με διαφορετικές παραμέτρους στον ίδιο κόμβο. Με άλλα λόγια, υπάρχει ανάγκη για έναν δικό του τύπο πόρων.

Για παράδειγμα, για να εγκαταστήσουμε τη λειτουργική μονάδα PHP, κάνουμε τα εξής στο Avito:

  1. Εγκαταστήστε το πακέτο με αυτήν την ενότητα.
  2. Ας δημιουργήσουμε ένα αρχείο ρυθμίσεων για αυτήν την ενότητα.
  3. Δημιουργούμε έναν συμβολικό σύνδεσμο στη διαμόρφωση για php-fpm.
  4. Δημιουργούμε έναν συμβολικό σύνδεσμο προς τη διαμόρφωση για το php cli.

Σε τέτοιες περιπτώσεις, ένα σχέδιο όπως π.χ καθορίζω (define, defined type, defined resource type). Το Define είναι παρόμοιο με μια κλάση, αλλά υπάρχουν διαφορές: πρώτον, κάθε Define είναι ένας τύπος πόρου, όχι ένας πόρος. Δεύτερον, κάθε ορισμός έχει μια σιωπηρή παράμετρο $title, όπου πηγαίνει το όνομα του πόρου όταν δηλώνεται. Όπως και στην περίπτωση των κλάσεων, πρέπει πρώτα να περιγραφεί ένας ορισμός, μετά τον οποίο μπορεί να χρησιμοποιηθεί.

Ένα απλοποιημένο παράδειγμα με μια ενότητα για PHP:

define php74::module (
  $php_module_name = $title,
  $php_package_name = "php7.4-${title}",
  $version = 'installed',
  $priority = '20',
  $data = "extension=${title}.son",
  $php_module_path = '/etc/php/7.4/mods-available',
) {
  package { $php_package_name:
    ensure          => $version,
    install_options => ['-o', 'DPkg::NoTriggers=true'],  # триггеры дебиановских php-пакетов сами создают симлинки и перезапускают сервис php-fpm - нам это не нужно, так как и симлинками, и сервисом мы управляем с помощью Puppet
  }
  -> file { "${php_module_path}/${php_module_name}.ini":
    ensure  => $ensure,
    content => $data,
  }
  file { "/etc/php/7.4/cli/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
  file { "/etc/php/7.4/fpm/conf.d/${priority}-${php_module_name}.ini":
    ensure  => link,
    target  => "${php_module_path}/${php_module_name}.ini",
  }
}

node server3.testdomain {
  php74::module { 'sqlite3': }
  php74::module { 'amqp': php_package_name => 'php-amqp' }
  php74::module { 'msgpack': priority => '10' }
}

Ο ευκολότερος τρόπος για να εντοπίσετε το σφάλμα δήλωσης διπλότυπης είναι στο Define. Αυτό συμβαίνει εάν ένας ορισμός έχει έναν πόρο με ένα σταθερό όνομα και υπάρχουν δύο ή περισσότερες περιπτώσεις αυτού του ορισμού σε κάποιον κόμβο.

Είναι εύκολο να προστατευτείτε από αυτό: όλοι οι πόροι εντός του ορισμού πρέπει να έχουν ένα όνομα ανάλογα $title. Μια εναλλακτική είναι η ανεπαρκής προσθήκη πόρων· στην απλούστερη περίπτωση, αρκεί να μετακινήσετε τους κοινούς πόρους σε όλες τις περιπτώσεις του ορισμού σε μια ξεχωριστή κλάση και να συμπεριλάβετε αυτήν την κατηγορία στον ορισμό - συνάρτηση include ανίκανος.

Υπάρχουν άλλοι τρόποι για την επίτευξη αδυναμίας κατά την προσθήκη πόρων, δηλαδή με τη χρήση συναρτήσεων defined и ensure_resources, αλλά θα σας το πω στο επόμενο επεισόδιο.

Εξαρτήσεις και ειδοποιήσεις για κλάσεις και ορισμούς

Οι κλάσεις και οι ορισμοί προσθέτουν τους ακόλουθους κανόνες στον χειρισμό εξαρτήσεων και ειδοποιήσεων:

  • Η εξάρτηση από μια κλάση/καθορίζει προσθέτει εξαρτήσεις από όλους τους πόρους της κλάσης/καθορίζει.
  • μια εξάρτηση κλάσης/ορισμού προσθέτει εξαρτήσεις σε όλους τους πόρους κλάσης/ορισμού.
  • Η ειδοποίηση class/define ειδοποιεί όλους τους πόρους της κλάσης/καθορίζει.
  • Η συνδρομή class/define εγγράφεται σε όλους τους πόρους της κατηγορίας/define.

Δηλώσεις υπό όρους και επιλογείς

Τεκμηρίωση εδώ.

if

Όλα είναι απλά εδώ:

if ВЫРАЖЕНИЕ1 {
  ...
} elsif ВЫРАЖЕНИЕ2 {
  ...
} else {
  ...
}

εκτός

εκτός αν είναι ένα if αντίστροφα: το μπλοκ κώδικα θα εκτελεστεί εάν η έκφραση είναι ψευδής.

unless ВЫРАЖЕНИЕ {
  ...
}

περίπτωση

Ούτε εδώ υπάρχει τίποτα περίπλοκο. Μπορείτε να χρησιμοποιήσετε κανονικές τιμές (συμβολοσειρές, αριθμούς, κ.λπ.), κανονικές εκφράσεις και τύπους δεδομένων ως τιμές.

case ВЫРАЖЕНИЕ {
  ЗНАЧЕНИЕ1: { ... }
  ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
  default: { ... }
}

Επιλογείς

Ένας επιλογέας είναι μια κατασκευή γλώσσας παρόμοια με case, αλλά αντί να εκτελέσει ένα μπλοκ κώδικα, επιστρέφει μια τιμή.

$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }

Ενότητες

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

Επιπλέον, υπάρχει το πρόβλημα της επαναχρησιμοποίησης του κώδικα - όταν όλος ο κώδικας βρίσκεται σε ένα μανιφέστο, είναι δύσκολο να μοιραστείτε αυτόν τον κώδικα με άλλους. Για να λύσει αυτά τα δύο προβλήματα, το Puppet έχει μια οντότητα που ονομάζεται modules.

Ενότητες - πρόκειται για σύνολα κλάσεων, ορισμών και άλλων οντοτήτων Puppet που τοποθετούνται σε ξεχωριστό κατάλογο. Με άλλα λόγια, μια ενότητα είναι ένα ανεξάρτητο κομμάτι της λογικής του Puppet. Για παράδειγμα, μπορεί να υπάρχει μια ενότητα για εργασία με nginx και θα περιέχει ό,τι και μόνο χρειάζεται για να εργαστείτε με το nginx ή μπορεί να υπάρχει μια ενότητα για εργασία με PHP κ.λπ.

Οι μονάδες έχουν εκδοθεί και υποστηρίζονται επίσης οι εξαρτήσεις των μονάδων μεταξύ τους. Υπάρχει ένα ανοιχτό αποθετήριο μονάδων - Puppet Forge.

Στον διακομιστή μαριονέτας, οι μονάδες βρίσκονται στον υποκατάλογο λειτουργιών του ριζικού καταλόγου. Μέσα σε κάθε λειτουργική μονάδα υπάρχει ένα τυπικό σχήμα καταλόγου - μανιφέστα, αρχεία, πρότυπα, lib και ούτω καθεξής.

Δομή αρχείου σε μια ενότητα

Η ρίζα της ενότητας μπορεί να περιέχει τους ακόλουθους καταλόγους με περιγραφικά ονόματα:

  • manifests - περιέχει μανιφέστα
  • files - περιέχει αρχεία
  • templates - περιέχει πρότυπα
  • lib — περιέχει κώδικα Ruby

Αυτή δεν είναι μια πλήρης λίστα καταλόγων και αρχείων, αλλά αρκεί για αυτό το άρθρο προς το παρόν.

Ονόματα πόρων και ονόματα αρχείων στη λειτουργική μονάδα

Τεκμηρίωση εδώ.

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

Οι κανόνες είναι απλοί:

  • Όλοι οι πόροι σε μια λειτουργική μονάδα πρέπει να βρίσκονται στο χώρο ονομάτων της λειτουργικής μονάδας. Εάν καλείται η ενότητα foo, τότε όλοι οι πόροι σε αυτό θα πρέπει να ονομαστούν foo::<anything>, ή απλά foo.
  • Ο πόρος με το όνομα της ενότητας πρέπει να βρίσκεται στο αρχείο init.pp.
  • Για άλλους πόρους, το σχήμα ονομασίας αρχείων έχει ως εξής:
    • το πρόθεμα με το όνομα της μονάδας απορρίπτεται
    • όλες οι διπλές άνω και κάτω τελείες, εάν υπάρχουν, αντικαθίστανται με κάθετες
    • προστίθεται επέκταση .pp

Θα δείξω με ένα παράδειγμα. Ας πούμε ότι γράφω μια ενότητα nginx. Περιέχει τους ακόλουθους πόρους:

  • κατηγορία nginx περιγράφεται στο δηλωτικό init.pp;
  • κατηγορία nginx::service περιγράφεται στο δηλωτικό service.pp;
  • καθορίζω nginx::server περιγράφεται στο δηλωτικό server.pp;
  • καθορίζω nginx::server::location περιγράφεται στο δηλωτικό server/location.pp.

πρότυπα

Σίγουρα γνωρίζετε τι είναι τα πρότυπα· δεν θα τα περιγράψω λεπτομερώς εδώ. Αλλά θα το αφήσω για κάθε ενδεχόμενο σύνδεσμος στη Wikipedia.

Τρόπος χρήσης προτύπων: Η έννοια ενός προτύπου μπορεί να επεκταθεί χρησιμοποιώντας μια συνάρτηση template, το οποίο περνά η διαδρομή προς το πρότυπο. Για πόρους του τύπου φιλέτο χρησιμοποιείται μαζί με την παράμετρο content. Για παράδειγμα, όπως αυτό:

file { '/tmp/example': content => template('modulename/templatename.erb')

Προβολή διαδρομής <modulename>/<filename> συνεπάγεται αρχείο <rootdir>/modules/<modulename>/templates/<filename>.

Επιπλέον, υπάρχει μια λειτουργία inline_template — λαμβάνει ως είσοδο το κείμενο του προτύπου, όχι το όνομα του αρχείου.

Μέσα στα πρότυπα, μπορείτε να χρησιμοποιήσετε όλες τις μεταβλητές Puppet στο τρέχον εύρος.

Το Puppet υποστηρίζει πρότυπα σε μορφή ERB και EPP:

Εν συντομία για το ERB

Δομές ελέγχου:

  • <%= ВЫРАЖЕНИЕ %> — εισάγετε την τιμή της έκφρασης
  • <% ВЫРАЖЕНИЕ %> — υπολογίστε την τιμή μιας παράστασης (χωρίς να την εισαγάγετε). Οι εντολές υπό όρους (αν) και οι βρόχοι (κάθε) συνήθως πηγαίνουν εδώ.
  • <%# КОММЕНТАРИЙ %>

Οι εκφράσεις στο ERB γράφονται σε Ruby (το ERB είναι στην πραγματικότητα Embedded Ruby).

Για να αποκτήσετε πρόσβαση σε μεταβλητές από το μανιφέστο, πρέπει να προσθέσετε @ στο όνομα της μεταβλητής. Για να καταργήσετε μια αλλαγή γραμμής που εμφανίζεται μετά από μια κατασκευή ελέγχου, πρέπει να χρησιμοποιήσετε μια ετικέτα κλεισίματος -%>.

Παράδειγμα χρήσης του προτύπου

Ας υποθέσουμε ότι γράφω μια ενότητα για τον έλεγχο του ZooKeeper. Η κλάση που είναι υπεύθυνη για τη δημιουργία του config μοιάζει κάπως έτσι:

class zookeeper::configure (
  Array[String] $nodes,
  Integer $port_client,
  Integer $port_quorum,
  Integer $port_leader,
  Hash[String, Any] $properties,
  String $datadir,
) {
  file { '/etc/zookeeper/conf/zoo.cfg':
    ensure  => present,
    content => template('zookeeper/zoo.cfg.erb'),
  }
}

Και το αντίστοιχο πρότυπο zoo.cfg.erb - Ετσι:

<% if @nodes.length > 0 -%>
<% @nodes.each do |node, id| -%>
server.<%= id %>=<%= node %>:<%= @port_leader %>:<%= @port_quorum %>;<%= @port_client %>
<% end -%>
<% end -%>

dataDir=<%= @datadir %>

<% @properties.each do |k, v| -%>
<%= k %>=<%= v %>
<% end -%>

Γεγονότα και ενσωματωμένες μεταβλητές

Συχνά το συγκεκριμένο μέρος της διαμόρφωσης εξαρτάται από το τι συμβαίνει αυτήν τη στιγμή στον κόμβο. Για παράδειγμα, ανάλογα με την έκδοση του Debian, πρέπει να εγκαταστήσετε μία ή άλλη έκδοση του πακέτου. Μπορείτε να παρακολουθήσετε όλα αυτά χειροκίνητα, η επανεγγραφή εμφανίζεται εάν αλλάξουν οι κόμβοι. Αλλά αυτή δεν είναι μια σοβαρή προσέγγιση· η αυτοματοποίηση είναι πολύ καλύτερη.

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

Ένα παράδειγμα εργασίας με γεγονότα:

notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог

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

Κατά τη λειτουργία, ο πράκτορας μαριονέτας αντιγράφει πρώτα όλους τους διαθέσιμους συλλέκτες δεδομένων από τον διακομιστή pappet στον κόμβο, μετά από τον οποίο τους εκκινεί και στέλνει τα δεδομένα που συλλέγονται στον διακομιστή. Μετά από αυτό, ο διακομιστής ξεκινά τη σύνταξη του καταλόγου.

Γεγονότα με τη μορφή εκτελέσιμων αρχείων

Τέτοια στοιχεία τοποθετούνται σε ενότητες στον κατάλογο facts.d. Φυσικά, τα αρχεία πρέπει να είναι εκτελέσιμα. Όταν εκτελούνται, πρέπει να εξάγουν πληροφορίες σε τυπική έξοδο είτε σε μορφή YAML είτε σε μορφή κλειδιού=τιμής.

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

#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'

Ρουμπίνια γεγονότα

Τέτοια στοιχεία τοποθετούνται σε ενότητες στον κατάλογο lib/facter.

# всё начинается с вызова функции Facter.add с именем факта и блоком кода
Facter.add('ladvd') do
# в блоках confine описываются условия применимости факта — код внутри блока должен вернуть true, иначе значение факта не вычисляется и не возвращается
  confine do
    Facter::Core::Execution.which('ladvdc') # проверим, что в PATH есть такой исполняемый файл
  end
  confine do
    File.socket?('/var/run/ladvd.sock') # проверим, что есть такой UNIX-domain socket
  end
# в блоке setcode происходит собственно вычисление значения факта
  setcode do
    hash = {}
    if (out = Facter::Core::Execution.execute('ladvdc -b'))
      out.split.each do |l|
        line = l.split('=')
        next if line.length != 2
        name, value = line
        hash[name.strip.downcase.tr(' ', '_')] = value.strip.chomp(''').reverse.chomp(''').reverse
      end
    end
    hash  # значение последнего выражения в блоке setcode является значением факта
  end
end

Γεγονότα κειμένου

Τέτοια γεγονότα τοποθετούνται σε κόμβους στον κατάλογο /etc/facter/facts.d σε παλιά Μαριονέτα ή /etc/puppetlabs/facts.d στο νέο Puppet.

examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue

Φτάνοντας στα γεγονότα

Υπάρχουν δύο τρόποι προσέγγισης των γεγονότων:

  • μέσα από το λεξικό $facts: $facts['fqdn'];
  • χρησιμοποιώντας το όνομα του γεγονότος ως όνομα μεταβλητής: $fqdn.

Είναι καλύτερο να χρησιμοποιήσετε ένα λεξικό $facts, ή ακόμα καλύτερα, υποδείξτε τον παγκόσμιο χώρο ονομάτων ($::facts).

Ακολουθεί η σχετική ενότητα της τεκμηρίωσης.

Ενσωματωμένες μεταβλητές

Εκτός από τα γεγονότα, υπάρχει επίσης κάποιες μεταβλητές, διαθέσιμο στον παγκόσμιο χώρο ονομάτων.

  • αξιόπιστα γεγονότα — μεταβλητές που λαμβάνονται από το πιστοποιητικό του πελάτη (καθώς το πιστοποιητικό εκδίδεται συνήθως σε διακομιστή poppet, ο πράκτορας δεν μπορεί απλώς να πάρει και να αλλάξει το πιστοποιητικό του, επομένως οι μεταβλητές είναι "έμπιστες"): το όνομα του πιστοποιητικού, το όνομα του κεντρικός υπολογιστής και τομέας, επεκτάσεις από το πιστοποιητικό.
  • γεγονότα διακομιστή —μεταβλητές που σχετίζονται με πληροφορίες σχετικά με τον διακομιστή—έκδοση, όνομα, διεύθυνση IP διακομιστή, περιβάλλον.
  • γεγονότα πράκτορα — μεταβλητές που προστίθενται απευθείας από τον πράκτορα μαριονέτας και όχι από τον παράγοντα — όνομα πιστοποιητικού, έκδοση πράκτορα, έκδοση μαριονέτας.
  • κύριες μεταβλητές - Μεταβλητές Pappetmaster (sic!). Είναι περίπου το ίδιο όπως στο γεγονότα διακομιστή, καθώς και οι τιμές των παραμέτρων διαμόρφωσης είναι διαθέσιμες.
  • μεταβλητές μεταγλωττιστή — μεταβλητές μεταγλωττιστή που διαφέρουν σε κάθε εύρος: το όνομα της τρέχουσας μονάδας και το όνομα της λειτουργικής μονάδας στην οποία έγινε πρόσβαση στο τρέχον αντικείμενο. Μπορούν να χρησιμοποιηθούν, για παράδειγμα, για να ελέγξετε ότι τα ιδιωτικά σας μαθήματα δεν χρησιμοποιούνται απευθείας από άλλες ενότητες.

Προσθήκη 1: πώς να τρέξετε και να διορθώσετε όλα αυτά;

Το άρθρο περιείχε πολλά παραδείγματα κώδικα μαριονέτας, αλλά δεν μας είπε καθόλου πώς να εκτελέσουμε αυτόν τον κώδικα. Λοιπόν, διορθώνω τον εαυτό μου.

Ένας πράκτορας είναι αρκετός για να τρέξει το Puppet, αλλά στις περισσότερες περιπτώσεις θα χρειαστείτε και διακομιστή.

Μέσο

Τουλάχιστον από την έκδοση XNUMX, πακέτα μαριονέτας-πράκτορας από επίσημο αποθετήριο Puppetlabs περιέχει όλες τις εξαρτήσεις (ρουμπίνι και τα αντίστοιχα πετράδια), επομένως δεν υπάρχουν δυσκολίες εγκατάστασης (μιλάω για διανομές που βασίζονται στο Debian - δεν χρησιμοποιούμε διανομές που βασίζονται σε RPM).

Στην απλούστερη περίπτωση, για να χρησιμοποιήσετε τη διαμόρφωση μαριονέτας, αρκεί να εκκινήσετε τον πράκτορα σε λειτουργία χωρίς διακομιστή: με την προϋπόθεση ότι ο κώδικας μαριονέτας έχει αντιγραφεί στον κόμβο, εκκινήστε puppet apply <путь к манифесту>:

atikhonov@atikhonov ~/puppet-test $ cat helloworld.pp 
node default {
    notify { 'Hello world!': }
}
atikhonov@atikhonov ~/puppet-test $ puppet apply helloworld.pp 
Notice: Compiled catalog for atikhonov.localdomain in environment production in 0.01 seconds
Notice: Hello world!
Notice: /Stage[main]/Main/Node[default]/Notify[Hello world!]/message: defined 'message' as 'Hello world!'
Notice: Applied catalog in 0.01 seconds

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

Μπορείτε να μιμηθείτε το μοντέλο ώθησης της εργασίας - μεταβείτε στον κόμβο που σας ενδιαφέρει και ξεκινήστε sudo puppet agent -t. Κλειδί -t (--test) περιλαμβάνει στην πραγματικότητα πολλές επιλογές που μπορούν να ενεργοποιηθούν μεμονωμένα. Αυτές οι επιλογές περιλαμβάνουν τα ακόλουθα:

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

Ο πράκτορας έχει έναν τρόπο λειτουργίας χωρίς αλλαγές - μπορείτε να τον χρησιμοποιήσετε όταν δεν είστε σίγουροι ότι έχετε γράψει τη σωστή διαμόρφωση και θέλετε να ελέγξετε τι ακριβώς θα αλλάξει ο πράκτορας κατά τη λειτουργία. Αυτή η λειτουργία ενεργοποιείται από την παράμετρο --noop στη γραμμή εντολών: sudo puppet agent -t --noop.

Επιπλέον, μπορείτε να ενεργοποιήσετε το αρχείο καταγραφής εντοπισμού σφαλμάτων της εργασίας - σε αυτό, η κούκλα γράφει για όλες τις ενέργειες που εκτελεί: για τον πόρο που επεξεργάζεται αυτήν τη στιγμή, για τις παραμέτρους αυτού του πόρου, για τα προγράμματα που εκκινεί. Φυσικά αυτό είναι μια παράμετρος --debug.

Διακομιστή

Δεν θα εξετάσω την πλήρη εγκατάσταση του διακομιστή pappet και την ανάπτυξη κώδικα σε αυτόν σε αυτό το άρθρο. Θα πω μόνο ότι εκτός του κουτιού υπάρχει μια πλήρως λειτουργική έκδοση του διακομιστή που δεν απαιτεί πρόσθετες ρυθμίσεις για να λειτουργήσει με μικρό αριθμό κόμβους (ας πούμε, μέχρι εκατό). Ένας μεγαλύτερος αριθμός κόμβων θα απαιτήσει συντονισμό - από προεπιλογή, ο puppetserver εκκινεί όχι περισσότερους από τέσσερις εργαζόμενους, για μεγαλύτερη απόδοση πρέπει να αυξήσετε τον αριθμό τους και μην ξεχάσετε να αυξήσετε τα όρια μνήμης, διαφορετικά ο διακομιστής θα συλλέγει σκουπίδια τις περισσότερες φορές.

Ανάπτυξη κώδικα - αν το χρειάζεστε γρήγορα και εύκολα, τότε δείτε (στο r10k)[https://github.com/puppetlabs/r10k], για μικρές εγκαταστάσεις θα πρέπει να είναι αρκετά.

Προσθήκη 2: Οδηγίες κωδικοποίησης

  1. Τοποθετήστε όλη τη λογική σε κλάσεις και ορισμούς.
  2. Διατηρήστε τις κλάσεις και τους ορισμούς σε λειτουργικές μονάδες, όχι σε δηλώσεις που περιγράφουν κόμβους.
  3. Χρησιμοποιήστε τα γεγονότα.
  4. Μην κάνετε ifs με βάση ονόματα κεντρικών υπολογιστών.
  5. Μη διστάσετε να προσθέσετε παραμέτρους για κλάσεις και ορισμούς - αυτό είναι καλύτερο από την σιωπηρή λογική που κρύβεται στο σώμα της κλάσης/ορισμού.

Θα εξηγήσω γιατί συνιστώ να το κάνετε αυτό στο επόμενο άρθρο.

Συμπέρασμα

Ας τελειώσουμε με την εισαγωγή. Στο επόμενο άρθρο θα σας μιλήσω για τα Hiera, ENC και PuppetDB.

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

Στην πραγματικότητα, υπάρχει πολύ περισσότερο υλικό - μπορώ να γράψω άρθρα για τα ακόλουθα θέματα, να ψηφίσω τι θα σας ενδιέφερε να διαβάσετε:

  • 59,1%Προηγμένες κατασκευές μαριονέτας - μερικές σκατά επόμενου επιπέδου: βρόχοι, χαρτογράφηση και άλλες εκφράσεις λάμδα, συλλέκτες πόρων, εξαγόμενοι πόροι και επικοινωνία μεταξύ κεντρικών υπολογιστών μέσω Puppet, ετικέτες, παρόχοι, αφηρημένοι τύποι δεδομένων.13
  • 31,8%"Είμαι ο διαχειριστής της μητέρας μου" ή πώς κάναμε φίλοι στο Avito με πολλούς διακομιστές poppet διαφορετικών εκδόσεων και, κατ 'αρχήν, το μέρος σχετικά με τη διαχείριση του διακομιστή poppet.7
  • 81,8%Πώς γράφουμε κώδικα μαριονέτας: όργανα, τεκμηρίωση, δοκιμές, CI/CD.18

Ψήφισαν 22 χρήστες. 9 χρήστες απείχαν.

Πηγή: www.habr.com