Dalsza część kursu traktującego o najpopularniejszym narzędziu o zarządzania konfiguracją infrastruktury.
Git.
Aby nasz Puppet mógł współpracować z Gitem potrzebujemy mieć tam założone w pierwszej kolejności konto. Po założeniu konta generujemy klucz ssh, który później wgramy na Gita.
1 |
# ssh-keygen -t rsa -b 4096 -C "name@example.com" |
Wyświetlamy wygenerowany klucz publiczny.
1 2 |
# cat /root/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDHVMyMyR66Sf4UI0xU3bHStMujeGWIKUMxxgOWd8o+rRu0/tvkf2E6AB0blYbKHNwU469rqvUs/mJTWmZYLB45Do52ODUFR25Ypa6039BE3g96OKntdTMwzkj4qhUJMC1GG50VSicsE6BNj9qGWDPmCl1Ri+tg+laBjpYZL2JTVXKAmun7eMtKyO1FarMMsJvvBLqVa3annOxUKMQSYJVolr6YvFQOjcqgK4Ivv4QF7J4D/4Y+Sb3wtb/qjHCN5XAZpcdPeTyyjY6nfpQmvWBWZxsn+BgK9luugf8igXR+1+oF6lzTUub1aVEbPdUau/n+WHjZDJ1k7Eyrc9aFk9PiC5IeUKnbjfgwt5hxurVxc4Sf2iwjTuY7am2z8qNKUrTtOSuL4h5wtTrwj55m91Kgg/axgodbsh0UZmDoq99vBFOgtQxMlrf3G+vNPzLaNJRFqNzfiqUmiwO301lA0+i8aCMSyfKjtNBzejiJ5vNy33Jh6QbuLvMV2AFeRZcVCfB9T8MYn194ModE2eHH6jEaa3hypW4rPwKPKhPbONjL+RYLFUU5AxO7YStH3U20A7/GkcNK6WQ3X00xy1asiME0/AV5S4MOl/z471I8YYneBoMqJ48BvfPe7xFB2Ay2uEqLUbT4FzVXkElG/mEXqE2WotSHbqSWgtPsav2r/EH33Q== name@example.com |
A następnie wklejamy go na Git hubie (www.github.com) w sekcji
Settings -> SSH and GPG keys -> New SSH key.
Tworzymy także nasze repozytorium o nazwie np. puppet-nginx.
Settings -> Repositories -> + -> New repository -> Repository name
Na naszej maszynie instalujemy gita.
1 |
yum -y install git |
Testujemy nasze repozytorium.
1 2 |
# cd /tmp # git clone git@github.com:GIT_ACCOUNT_NAME/puppet-nginx.git |
Gdzie GIT_ACCOUNT_NAME to nazwa naszego konta na githubie, a puppet-ssh to to nazwa repozytorium.
Moduły.
Aby manifest był bardziej czytelny i łatwiejszy w utrzymaniu dobrym zwyczajem jest rozbicie go na moduły.
Generowanie modułu:
1 |
# puppet module generate <MAINTAINER>-<MODULE_NAME> |
Nazwa modułu powinna:
- zawierać male litery,
- zawierać liczby,
- zawierać podkreślenia “_”,
- zaczynać się od małej litery,
- nie może zawierać dwukropek “::”.
Wejście do katalogu z modułami:
1 |
# cd /etc/puppetlabs/code/environments/production/modules |
Wyświetlenie aktualnie zainstalowanych modułów:
1 2 |
# ls apache concat ntp stdlib |
Wygenerowanie naszego modułu o nazwie np. ssh (zamiast username wpisujemy nazwę użytownika):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
[root@ppmaster modules]# puppet module generate username-nginx We need to create a metadata.json file for this module. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank. Puppet uses Semantic Versioning (semver.org) to version modules. What version is this module? [0.1.0] --> Who wrote this module? [username] --> My Name <email@address.com> What license does this module code fall under? [Apache-2.0] --> How would you describe this module in a single sentence? --> Module is associated with NGINX. Where is this module's source code repository? --> https://github.com/<em>GIT_ACCOUNT_NAME</em>/puppet-nginx Where can others go to learn more about this module? [https://github.com/mborodziuk/pupp et-ssh] --> Where can others go to file issues about this module? [https://github.com/mborodziuk/pup pet-ssh/issues] --> ---------------------------------------- { "name": "username-nginx", "version": "0.1.0", "author": "My Name <email@address.com>", "summary": "Module is associated with NGINX.", "license": "Apache-2.0", "source": "https://github.com/<em>GIT_ACCOUNT_NAME</em>/puppet-nginx", "project_page": "https://github.com/<em>GIT_ACCOUNT_NAME</em>/puppet-nginx", "issues_url": "https://github.com/<em>GIT_ACCOUNT_NAME</em>/puppet-nginx/issues", "dependencies": [ {"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"} ], "data_provider": null } ---------------------------------------- About to generate this metadata; continue? [n/Y] --> Y Notice: Generating module at /etc/puppetlabs/code/environments/production/modules/nginx... Notice: Populating templates... Finished; module generated in nginx. nginx/Gemfile nginx/Rakefile nginx/examples nginx/examples/init.pp nginx/manifests nginx/manifests/init.pp nginx/spec nginx/spec/classes nginx/spec/classes/init_spec.rb nginx/spec/spec_helper.rb nginx/README.md nginx/metadata.json |
W katalogu z modułami pojawił się nowy moduł:
1 2 |
[root@ppmaster modules]# ls apache concat ntp nginx stdlib |
Szkielet naszego modułu składa się z takich plików i katalogów:
1 2 3 |
[root@ppmaster modules]# cd nginx [root@ppmaster ssh]# ls examples Gemfile manifests metadata.json Rakefile README.md spec |
W pliku metadata.json
znajdują się opcje wpisywane przez nas w czasie generowania modułu. Brakuje tutaj katalogu files
i templates
, które musimy utworzyć ręcznie.
Wyślijmy teraz do repozytorium git nasz moduł:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
[root@ppmaster ssh]# git init Initialized empty Git repository in /etc/puppetlabs/code/environments/production/modules/nginx/.git/ [root@ppmaster ssh]# git add . [root@ppmaster ssh]# git commit -am "Init commit" [master (root-commit) 62cd417] Init commit Committer: root <root@ppmaster.example.com> Your name and email address were configured automatically based on your username and hostname. Please check that they are accurate. You can suppress this message by setting them explicitly: git config --global user.name "Your Name" git config --global user.email you@example.com After doing this, you may fix the identity used for this commit with: git commit --amend --reset-author 8 files changed, 215 insertions(+) create mode 100644 Gemfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 examples/init.pp create mode 100644 manifests/init.pp create mode 100644 metadata.json create mode 100644 spec/classes/init_spec.rb create mode 100644 spec/spec_helper.rb [root@ppmaster ssh]# git remote add origin git@github.com:mborodziuk/puppet-nginx.git [root@ppmaster ssh]# git push origin master Warning: Permanently added the RSA host key for IP address '192.30.253.113' to the list of known hosts. Counting objects: 14, done. Delta compression using up to 4 threads. Compressing objects: 100% (11/11), done. Writing objects: 100% (14/14), 4.02 KiB | 0 bytes/s, done. Total 14 (delta 0), reused 0 (delta 0) To git@github.com:<em>GIT_ACCOUNT_NAME</em>/puppet-nginx.git * [new branch] master -> master |
Klasy.
To nazwane bloki kodu Puppeta, które są używane w modułach. Nie są używane dopóki nie zostaną wywołane. Mogą być dodane w katalogu węzła przez zadeklarowanie ich w manifeście. Używają zasobów do konfigurowania pakietów, plików, usług, etc. Używają parametrów do korzystania z zewnętrznych danych. Mogą być użyte na danym węźle tylko jeden raz.
Składnia klasy wygląda następująco:
1 2 3 4 5 |
class <CLASS_NAME> ( <DATA_TYPE> <PARAM_NAME> ){ ... puppet code ... } |
Nazwa klasy składa się z segmentów, które mogą zwierać tylko małe litery, cyfry i podkreślenia. Segmenty łączyć możemy podwójnym znakiem dwukropka “::”. Pierwszy segment nazwy klasy określa moduł. Ostatni segment określa nazwę pliku. Każdy dodatkowy segment w nazwie pomiędzy pomiędzy pierwszym i ostatnim członem nazwy będzie dodatkowym podkatalogiem, np.
apache - <MODULE_DIRECTORY>/apache/manifest/init.pp
apache::mod - <MODULE_DIRECTORY>/apache/manifest/mod.pp
apache::mod::passenger - <MODULE_DIRECTORY>/apache/manifest/mod/passenger.pp
Gdzie <MODULE_DIRECTORY>
to /etc/puppetlabs/code/environments/production/modules
Po wygenerowaniu przez nas wcześniej modułu ssh definicja klasy ssh znajduje się w pliku /etc/puppetlabs/code/environments/production/modules/ssh/manifest/init.pp
.
Modyfikujemy ją tak, aby wyglądała następująco:
1 2 3 4 |
class nginx { class { 'nginx::install': } -> class { 'nginx::service': } } |
Każda klasa powinna znajdować się w swoim własnym pliku z rozszerzeniem .pp. W przeciwnym razie Puppet zwróci komunikat o błędzie.
Zakładamy zatem plik /etc/puppetlabs/code/environments/production/modules/nginx/manifest/install.pp
o treści:
1 2 3 4 5 6 7 8 9 10 11 |
class nginx::install { package { 'epel-release': ensure => installed, }-> package { 'httpd': ensure => absent, }-> package { 'nginx': ensure => present, } } |
oraz w tym samym katalogu plik service.pp
:
1 2 3 4 5 6 7 8 |
class nginx::service { service { 'nginx': ensure => running, enable => true, hasstatus => true, hasrestart => true, } } |
Sprawdzamy składnie naszej klasy:
1 2 3 4 5 |
[root@ppmaster manifests]# puppet parser validate init.pp [root@ppmaster manifests]# puppet parser validate install.pp [root@ppmaster manifests]# puppet parser validate service.pp [root@ppmaster manifests]# puppet parser validate user.pp [root@ppmaster manifests]# |
Na wyjściu nie ma żadnych komunikatów a zatem składnia jest poprawna. Tym samym cały kod dotyczący nginx przenieśliśmy z pliku site.pp
do wygenerowanego modułu.
Plik /etc/puppetlabs/code/environments/production/manifests/site.pp
możemy teraz zmodyfikować tak aby wczytywał tylko utworzony przez nas wcześniej moduł nginx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
node 'ppagent1.example.com' { include nginx } node default { user { 'user1': ensure => present, comment => 'First and Second Name', home => '/home/user1', managehome => true, } ssh_authorized_key { 'user1': user => 'user1', type => 'rsa', key => 'AAAAB3NzaC1yc2EAAAABIwAAAIEA3ATqENg+GWACa2BzeqTdGnJhNoBer8x6pfWkzNzeM8Zx7/2Tf2pl7kHdbsiTXEUawqzXZQtZzt/j3Oya+PZjcRpWNRzprSmd2UxEEPTqDw9LqY5S2B8og/NyzWaIYPsKoatcgC7VgYHplcTbzEhGu8BsoEVBGYu3IRy5RkAcZik=', } } |
Operatory.
=
Przypisanie==
Równość
1 |
$eggs == '61' |
=!
Nierówność
1 |
$username != 'FOTHERINGTON-THOMAS' |
>
większy niż-
<
mniejszy niż >=
większy lub równy niż<=
mniejszy lub równy niż
1 2 3 |
if $eggs >= 61 { notify { 'YOU KNOW I GOT NO SENSE OF EGGS': } } |
Operator porównania występuje również w łańcuchach, jest to operator “in
“:
1 2 3 |
if 'eggs' in 'Can you believe the price of eggs?' { ... } |
- Boolean
Przykłady:
1 2 3 4 |
$eggs > 61 and $eggs < 100 $eggs > 61 or $today == 'Thursday' $today == 'Thursday' and ($eggs < 61 or $eggs > 100) ($today == 'Thursday' and $eggs < 61) or $eggs > 100 |
- Arytmetyka: dodawanie (
+
), odejmowanie (-
), mnożenie (*
), dzielenie (/
).
1 |
$celsius = ($fahrenheit - 32) * 5 / 9 |
Wyrażenia warunkowe.
Bardzo użyteczne jest wykonywanie różnych operacji w zależności od wartości pewnych zmiennych lub wyrażeń. Puppet pozwala robić to na wiele różnych sposobów.
- If, elsif, else
1 2 3 4 5 6 7 8 9 |
if EXPRESSION { OPTIONAL_SOMETHING } elsif ANOTHER_EXPRESSION { OPTIONAL_SOMETHING_ELSE } else { OPTIONAL_OTHER_THING } |
Przykład:
1 2 3 4 5 6 7 8 9 |
if $::processorcount >= 16 { include cpu_intensive_application } elsif $::processorcount >= 4 { include medium_application } else { include lightweight_application } |
- unless
1 2 3 |
unless EXPRESSION { OPTIONAL_SOMETHING } |
- case
1 2 3 4 5 6 7 |
case EXPRESSION { CASE1 { BLOCK1 } CASE2 { BLOCK2 } CASE3 { BLOCK3 } ... default : { ... } } |
Np. konstrukcję if:
1 2 3 4 5 6 7 8 9 10 11 12 |
if $::operatingsystem == 'Ubuntu' { include os_specific::ubuntu } elsif $::operatingsystem == 'Debian' { include os_specific::debian } elsif $::operatingsystem == 'RedHat' { include os_specific::redhat } else { include os_specific::default } |
można zastąpić konstrukcją case:
1 2 3 4 5 6 |
case $::operatingsystem { 'Ubuntu': { include os_specific::ubuntu } 'Debian': { include os_specific::debian } 'RedHat': { include os_specific::redhat } default : { include os_specific::default } } |
Inny przykład case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
case $::operatingsystem { 'Ubuntu': { $os_type = 'Debianlike' } 'RedHat', 'CentOS': { $os_type = 'Redhatlike' } 'Darwin': { $os_type = 'Mac OS' } default: { $os_type = 'UNKNOWN' } } notify { "You're running a ${os_type} system": } |
Facter.
Facter to narzędzie, które dostarcza informacje o systemie. Wszystkie dostępne informacje o systemie mogą być wyświetlone po wpisaniu komendy:
1 |
# facter |
Użyjmy factera w niedawno utworzonym module. W tym celu zakładamy i edytujemy plik /etc/puppetlabs/code/environments/production/modules/nginx/manifest/params.pp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class nginx::params { case $facts['os']['family'] { 'Debian': { $package_name = 'nginx' $service_name = 'nginx' } 'RedHat': { $package_name = 'nginx' $service_name = 'nginx' } 'default': { fail("${facts['operatingsystem']} is not supported") } } } |
Poza tym zmieniamy zawartość init.pp
:
1 2 3 4 5 6 7 8 |
class nginx( String $package_name = $::nginx::params::package_name, String $service_name = $::nginx::params::service_name, ) inherits ::nginx::params { class { 'nginx::install': } -> class { 'nginx::service': } } |
Zmieniamy także plik install.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class nginx::install ( String $package_name = $::nginx::params::package_name, ){ package { 'epel-release': ensure => installed, }-> package { 'httpd': ensure => absent, }-> package { 'nginx-package': ensure => present, name => $package_name, } } |
oraz service.pp
:
1 2 3 4 5 6 7 8 9 10 11 |
class nginx::service( String $service_name = $::nginx::params::service_name, ){ service { 'nginx-service': ensure => running, name => $service_name, enable => true, hasstatus => true, hasrestart => true, } } |
Walidacja poprawności składni:
1 2 3 4 |
[root@ppmaster manifests]# puppet parser validate init.pp [root@ppmaster manifests]# puppet parser validate params.pp [root@ppmaster manifests]# puppet parser validate install.pp [root@ppmaster manifests]# puppet parser validate service.pp |
Typy danych.
Podstawowe typy danych używane w Puppecie to:
- String – łańcuch
- Integer, Float, Numeric
- Boolean
- Array – tablica
- Hash
- Regexp
- Undef
- Default
Abstrakcyjne typy danych używane przez Puppeta:
- Scalar – inaczej
Integer, Float, String, Boolean, Regexp
- Collection – tablica lub hash
- Variant – dowolna ilość różnych typów danych
- Data – tak jak Scalar plus dodatkowo undef, tablice i hashe
- Pattern – wzorzec
- Enum – zawiera wcześniej określone stringi
- Tuple – tablica, która może zawierać różne typy danych
- Struct – zawiera hashe
- Optional – dowolny typ danych
- Catalogentry – zawiera zasoby i klasy
- Any – dowolna wartość dowolnego typu danych
- Callable – lambdy dostarczone jako argumenty funkcji
Wyrażenie regularne.
W Puppecie są różne sposoby na testowanie łańcuchów znaków.
Np.
1 2 3 |
if $role == 'webserver' { ... } |
lub
1 2 3 |
if 'dunk' in 'doughnuts' { ... } |
Jeżeli natomiast chcemy sprawdzić czy w łańcuchu występuje jakiś wzorzec to musimy się posłużyć wyrażeniami regularnymi.
Wzorzec pasuje: VALUE =~ /REGEX/
Wzorzec nie pasuje: VALUE !~ /REGEX/
Np. wyrażenie:
1 2 3 |
if $::hostname =~ /app.*staging/ { ... } |
będzie prawdziwe jeżeli string $::hostname przyjmie dowolną poniższą wartość:
- app_staging
- app-1-staging
- application_staging
- appstaging
- my_app_staging_server
Jeżeli zachodzi potrzeba odnieść się do tekstu, który był wyszukany przez wyrażenie regularne to można to zrobić zmienną $0
, np.
1 2 3 4 5 6 |
$uname = generate('/usr/bin/uname','-a') if $uname =~ /\d+\.\d+\.\d+/ { notify { "I have kernel version ${0}": } } Notice: I have kernel version 3.2.0 |
Jeżeli jest potrzeba odniesienia się do jakiejś części wyszukanego przez wzorzec tekstu to należy go zamknąć w nawias, np:
1 2 3 4 5 6 |
$uname = generate('/usr/bin/uname','-a') if $uname =~ /(\d+)\.\d+\.\d+/ { notify { "I have kernel version ${0}, major version ${1}": } } Notice: I have kernel version 3.2.0, major version 3 |
Odwołanie do każdego następnego nawiasu będzie się odbywać przez zmienne $2
, $3
itd.
Wyrażenia regularne zaimplementowane w Puppecie wywodzą się z języka Ruby. Więcej o ich składni można poczytać w tym tutorialu.
Wyrażenia regularne mogą być stosowane w określaniu węzłów, np. zamiast zapisu:
1 2 3 |
node 'demo1', 'demo2', 'demo3' { ... } |
możemy zapisać:
1 2 3 |
node /demo.*/ { ... } |
Przydaje się to gdy mamy pewną ilość serwerów wykonujących te same zadania o podobnych nazwach:
1 2 3 4 5 6 7 8 9 |
node /web.*/ { include webserver } node /app.*/ { include appserver } node /db.*/ { include dbserver } |
W wyrażeniach warunkowych także można użyć wyrażeń regularnych, np. zawartość params.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class nginx::params { case $facts['os']['family'] { /^(Debian|Ubuntu)$/: { $package_name = 'nginx' $service_name = 'nginx' } 'RedHat', 'CentOS': { $package_name = 'nginx' $service_name = 'nginx' } 'default': { fail("${facts['operatingsystem']} is not supported") } } } |
Tablice.
Przy pomocy tablic możemy grupować zasoby, np.:
1 2 3 |
package { [ 'php5-cli', 'php5-fpm', 'php-pear' ]: ensure => installed, } |
Przypisanie zmiennej tablicy:
1 |
$developers = ['jerry', 'george', 'elaine'] |
Wywołanie:
1 |
notify { $developers: } |
zwróci:
1 2 3 |
Notice: george Notice: jerry Notice: elaine |
Interpolacja tablicy w łańcuch:
1 2 3 4 |
$developers = ['jerry', 'george', 'elaine'] notify { "The developers are: ${developers}": } Notice: The developers are: jerrygeorgeelaine |
Odniesienie się do konkretnego elementu tablicy:
1 |
$developers[0] |
Można posługiwać się także ujemnymi indeksami:
1 2 3 4 |
$developers = ['jerry', 'george', 'elaine'] notify { "The last developer is: ${developers[-1]}": } Notice: The last developer is: elaine |
Operator in
działa również w tablicach:
1 2 3 |
if $crewmember in ['Frank', 'Dave'] { notify { "I'm sorry, ${crewmember}. I'm afraid I can't do that.": } } |
Odnosząc się do zmiennej w innym stringu zmienną owijamy w nawiasy klamrowe, np. ${crewmember}
.
Hashe.
Hashe to zbiór par elementów: klucz => wartość
. W Puppecie hash wygląda następująco:
1 2 3 4 |
$interfaces = { 'lo0' => '127.0.0.1', 'eth0' => '192.168.0.1', } |
Odniesienie się do konkretnej pozycji w hashu:
1 2 3 4 |
$address = $interfaces['eth0'] notify { "Interface eth0 has address ${address}": } Notice: Interface eth0 has address 192.168.0.1 |
Kluczem w hashu musi być string ale wartości mogą być dowolnego typu:
1 2 3 4 5 6 |
$contrived_example = { 'fish' => 'babel', 'answer' => 42, 'crew' => ['Ford Prefect', 'Arthur Dent'], 'hash' => { 'Warning' => 'Beware of the leopard' } } |
Ponieważ wartości w hashu mogą być także innymi hashami to można konstruować wielopoziomowe hashe, np:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$interfaces = { 'lo0' => { 'address' => '127.0.0.1', 'netmask' => '255.0.0.0', }, 'eth0' => { 'address' => '192.168.0.1', 'netmask' => '255.255.255.0', } } $eth0_netmask = $interfaces['eth0']['netmask'] notify { "eth0 has netmask ${eth0_netmask}": } Notice: eth0 has netmask 255.255.255.0 |
Sprawdzać czy dany klucz występuje w hashu możemy wyrażeniem:
1 2 3 |
if 'eth0' in $interfaces { ... } |
Pliki.
Nginx jest zainstalowany i uruchomiony ale nie serwuje strony web. Tworzymy na masterze zatem strukturę katalogów i plik index.html
dla serwowanej strony.
1 2 |
# mkdir -p /etc/puppetlabs/code/environments/production/modules/nginx/files/website # echo 'This is website' > /etc/puppetlabs/code/environments/production/modules/nginx/files/website/index.html |
Utworzenie pliku /etc/puppetlabs/code/environments/production/modules/nginx/files/example.conf
z konfiguracją strony:
1 2 3 4 5 |
server { listen 80; root /var/www/example; server_name example.com; } |
Zakładamy nowy plik /etc/puppetlabs/code/environments/production/modules/nginx/manifests/config.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class nginx::config { file { '/etc/nginx/default.d/default': ensure => file, mode => '0600', owner => 'root', group => 'root', source => 'puppet:///modules/nginx/example.conf', } file { '/var/www': ensure => directory, mode => '0755', owner => 'root', group => 'root', } file { '/var/www/example': source => 'puppet:///modules/nginx/files/website', recurse => true, require => File['/var/www'], } } |
Atrybut recurse
pozwala na skopiowanie całego katalogu website wraz z zawartością do /var/www pod nazwą example. Zwróćmy uwagę, że wartość atrybutu source puppet:///modules/nginx/example.conf
zaczyna się od trzech ukośników a nie dwóch co oznacza, że plik puppeta, na który wskazuje atrybut jest na lokalnym hoście, ponieważ zapis puppet:///modules/nginx/example.conf
jest równoważny z puppet:///modules/nginx/example.conf.
Edytujemy plik init.pp
.
1 2 3 4 5 6 7 8 9 |
class nginx( String $package_name = $::nginx::params::package_name, String $service_name = $::nginx::params::service_name, ) inherits ::nginx::params { class { 'nginx::install': } -> class { 'nginx::config': } ~> class { 'nginx::service': } } |
Znak ~> to odpowiednik atrybutu notify => Service['nginx']
, nakazuje puppetowi aby zrestartował się po każdej zmianie zawartości pliku example.conf
. Kod jest równoważny z:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
node 'ppagent1.example.com' { package { 'epel-release': ensure => installed, }-> package { 'httpd': ensure => absent, }-> package { 'nginx': ensure => installed, } service { 'nginx': ensure => running, require => Package['nginx'], } file { '/etc/nginx/default.d/default': source => 'puppet:///etc/puppetlabs/code/environments/production/example.conf', notify => Service['nginx'], } } |
Co z koleji jest równoważne z:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
node 'ppagent1.example.com' { package { 'epel-release': ensure => installed, }-> package { 'httpd': ensure => absent, }-> package { 'nginx': ensure => installed, } file { '/etc/nginx/default.d/default': source => 'puppet:///etc/puppetlabs/code/environments/production/example.conf', } service { 'nginx': ensure => running, require => Package['nginx'], subscribe => File['/etc/nginx/default.d/default'], } } |
Czyli usługa niginx będzie subskrybować plik /etc/nginx/default.d/default
. Jeżeli zmieni się zawartość tego pliku to usługa się przeładuje.
Do zarządzania konfiguracją wybranego pakietu często używany jest następujący wzorzec.
1 2 3 4 5 6 7 8 9 10 11 |
package { THE_STUFF: ensure => installed, } service { THE_STUFF: ensure => running, require => Package[THE_STUFF], } file { '/etc/THE_STUFF.conf': source => 'puppet:///puppet/modules/THE_STUFF/THE_STUFF.conf', notify => Service[THE_STUFF], } |
W praktyce Puppet jest wolny jeżeli chodzi o pobieranie całego drzewa katalogów pod stronę web dlatego lepiej używać GITa jako repozytorium.
Szablony.
Użytego przez nas pliku konfiguracyjnego nginxa example.com
nie możemy niestety wykorzystać ponownie ponieważ zawiera on konfigurację tylko dla ściśle określonego jednego webserwera. Aby można go było użyć wielokrotnie musimy przerobić go na szablon.
Szablony mają roszerzenie .erb
i składujemy w katalogu:
1 |
<MODULES DIRECTORY>/<MODULE NAME>/templates/motd.erb |
ERB to język tworzenia szablonów używany przez Ruby.
Zakładamy katalog z szablonami nginxa i wgrywamy do niego nasz plik:
1 2 |
# mkdir /etc/puppetlabs/code/environments/production/modules/nginx/templates # mv /etc/puppetlabs/code/environments/production/modules/nginx/files/example.com /etc/puppetlabs/code/environments/production/modules/nginx/templates/vhost.conf.erb |
Edytujemy plik vhost.conf.erb
:
1 2 3 4 5 |
server { listen 80; root /var/www/<%= @site_name %>; server_name <%= @site_domain %>; } |
Znaki <%= %>
oznaczają, że w środku będzie zmienna w języku Ruby. W naszym przypadku będzie tu wstawiona zmienna Ruby. Zmienne te to @site_name
oraz @site_domain
. Istnieje także możliwość wstawienia całych wyrażeń w języku Ruby. Robi się to przy pomocy konstrukcji, np.:
1 |
<% if condition %> ...text.. <% end %> |
Inne konstrukcje, które można używać w szablonach ERB to:
- komentarz:
1<%# This is a comment %> - pętla:
123<% @valuse.each do |value -%>some value <%= value %><% end -%>
Nasz plik config.pp
wyglądać powinien teraz tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class nginx::config { $site_name = 'example1' $site_domain = 'example1.com' file { '/etc/nginx/default.d/default': ensure => absent, }-> file { '/etc/nginx/default.d/example1.com': ensure => file, mode => '0600', owner => 'root', group => 'root', content => template('nginx/vhost.conf.erb'), } file { '/var/www': ensure => directory, mode => '0755', owner => 'root', group => 'root', } file { '/var/www/example': source => 'puppet:///modules/nginx/files/website', recurse => true, require => File['/var/www'], } } |
Dodajmy teraz tryb warunkowy do naszej konfiguracji web serwera aby mógł działać nie tylko na porcie 80. Nasz szablon erb niech wygląda tak:
1 2 3 4 5 |
server { <% if @standard_port %> listen 80; <% else %> listen 8080; <% end %> root /var/www/<%= @site_name %>; server_name <%= @site_domain %>; } |
Oczywiście można było wpisać tutaj po prostu:
1 |
listen <%= @port %>; |
ale chcemy wykorzystać tryb warunkowy if.
Pliki tworzące moduł nginx niech wyglądają teraz jak poniżej.
params.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class nginx::params { $standard_port = true case $facts['os']['family'] { 'Debian': { $package_name = 'nginx' $service_name = 'nginx' } 'RedHat': { $package_name = 'nginx' $service_name = 'nginx' } 'default': { fail("${facts['operatingsystem']} is not supported") } } } |
init.pp
:
1 2 3 4 5 6 7 8 9 10 11 |
class nginx( Boolean $standard_port = $::nginx::params::standard_port, String $package_name = $::nginx::params::package_name, String $service_name = $::nginx::params::service_name, ) inherits ::nginx::params { class { 'nginx::install': } -> class { 'nginx::user': } -> class { 'nginx::config': } ~> class { 'nginx::service': } } |
config.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class nginx::config( $standard_port = $::nginx::standard_port, ){ $site_name = 'example1' $site_domain = 'example1.com' file { '/etc/nginx/default.d/default': ensure => absent, }-> file { '/etc/nginx/default.d/example1.com': ensure => file, mode => '0600', owner => 'root', group => 'root', content => template('nginx/vhost.conf.erb'), } file { '/var/www': ensure => directory, mode => '0755', owner => 'root', group => 'root', } file { '/var/www/example': source => 'puppet:///modules/nginx/website', recurse => true, require => File['/var/www'], } } |
Parametry klasy.
Ponieważ używany przez nas wcześniej wzorzec przekazywania parametrów do klasy <MODULE NAME>::params został zdeprecjonowany musimy te parametry przekazać w inny sposób. Rekomendowanym sposobem jest teraz używanie funkcji lub Hiera, o której napisze w innych artykułach. Funkcja, o które mowa to Function Data Provider – <MODULE NAME>::data, podobna do używanej wcześniej params.pp.
Function Data Provider może być:
- napisana w języku Puppeta i zlokalizowana w pliku
<MODULE ROOT>/functions/data.pp
- napisana w języku Ruby i zlokalizowana w
<MODULE ROOT>/lib/puppet/functions/<MODULE NAME>/data.rb
.
Stwórzmy teraz taką funkcję napisaną w języku Puppeta.
1 2 3 4 |
# cd /etc/puppetlabs/code/environments/production/modules/nginx # mkdir functions # cd functions # vim data.pp |
Plik data.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function nginx::data { $base_params = { 'nginx::ensure' => 'present', 'nginx::service_enable' => true, 'nginx::service_ensure' => 'running', 'nginx::site_name' => 'example3', 'nginx::site_domain' => 'example3.com', 'nginx::standard_port' => true, } case $facts['os']['family'] { 'Debian': { $os_params = { 'nginx::package_name' => 'nginx', 'nginx::service_name' => 'nginx', } } 'RedHat': { $os_params = { 'nginx::package_name' => 'nginx', 'nginx::service_name' => 'nginx', } } default: { fail("${facts['operatingsystem']} is not supported") } } $base_params + $os_params } |
init.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class nginx( String $package_name, String $service_name, String $ensure, String $service_ensure, String $site_name, String $site_domain, Boolean $service_enable, Boolean $standard_port, ) { class { 'nginx::install': } -> class { 'nginx::config': } ~> class { 'nginx::service': } } |
config.pp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class nginx::config( Boolean $standard_port = $::nginx::standard_port, String $site_name = $::nginx::site_name, String $site_domain = $::nginx::site_domain, ){ file { '/etc/nginx/default.d/default': ensure => absent, }-> file { "/etc/nginx/default.d/${site_domain}": ensure => file, mode => '0600', owner => 'root', group => 'root', content => template('nginx/vhost.conf.erb'), } file { '/var/www': ensure => directory, mode => '0755', owner => 'root', group => 'root', } file { "/var/www/${site_name}": source => 'puppet:///modules/nginx/website', recurse => true, require => File['/var/www'], } } |
install.pp:
1 2 3 4 5 6 7 8 9 10 11 |
class nginx::install ( String $package_name = $::nginx::package_name, String $ensure = $::nginx::ensure, ){ package { 'epel-release': ensure => installed, }-> package { 'httpd': ensure => absent, }-> package { 'nginx-package': ensure => $ensure, name => $package_name, } } |
service.pp:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class nginx::service( String $service_name = $::nginx::service_name, String $service_ensure = $::nginx::service_ensure, Boolean $service_enable = $::nginx::service_enable, ){ service { 'nginx-service': ensure => $service_ensure, name => $service_name, enable => $service_enable, hasstatus => true, hasrestart => true, } } |
Zmodyfikować musimy także plik metadata.json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "name": "username-nginx", "version": "0.1.0", "author": "mborodziuk", "summary": null, "license": "Apache-2.0", "source": "https://github.com/mborodziuk/puppet-nginx", "project_page": "https://github.com/mborodziuk/puppet-nginx", "issues_url": "https://github.com/mborodziuk/puppet-nginx/issues", "dependencies": [ {"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"} ], "data_provider": "function" } |
data_provider został tu przestawiony na function.
Jak widać wszystkie parametry modułu nginx można ustawiawiono bezpośrednio w data.pp
.
Definicje zasobów.
Definicje zasobów przydają się gdy chcemy grupować zasoby różnych typów. Jeżeli mamy potrzebę wykonania np. takich zadań:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
file { '/usr/local/bin/job1': source => 'puppet:///modules/scripts/job1', mode => '0755', } cron { 'Run job1': command => '/usr/local/bin/job1', hour => '00', minute => '00', } file { '/usr/local/bin/job2': source => 'puppet:///modules/scripts/job2', mode => '0755', } cron { 'Run job2': command => '/usr/local/bin/job2', hour => '00', minute => '00', } |
To najlepiej zdefiniować taki zasób:
1 2 3 4 5 6 7 8 9 10 11 12 |
# Manages a script plus the cron job to run it define script_job() { file { "/usr/local/bin/${name}": source => "puppet:///modules/scripts/${name}", mode => '0755', } cron { "Run ${name}": command => "/usr/local/bin/${name}", hour => '00', minute => '00', } } |
A później go uruchomić w manifeście:
1 2 |
script_job { 'backup_database': } |
Można także przekazywać parametry do takiego zdefiniowanego zasobu:
1 2 3 4 5 6 7 8 9 10 11 |
define script_job( $hour = '00', $minute = '00') { file { "/usr/local/bin/${name}": source => "puppet:///modules/scripts/${name}", mode => '0755', } cron { "Run ${name}": command => "/usr/local/bin/${name}", hour => $hour, minute => $minute, } } |
Uruchomienie z domyślnymi parametrami ($hour = '00', $minute = '00'
):
1 2 |
script_job { 'backup_database': } |
Uruchomienie ze swoimi parametrami:
1 2 3 4 |
script_job { 'backup_database': hour => '05', minute => '30', } |
Uruchomienie z jednym domyślnym ($minute = '00'
) i jednym swoim parametrem (hour => "*"
):
1 2 3 |
script_job { 'download_tweets': hour => "*", } |
Utwórzmy taki zdefiniowany zasób w naszym module nginx, który będzie odpowiedzialny za backup plików tworzących webserver example.com. Backup będzie wykonywał skrypt /etc/puppetlabs/code/environments/production/modules/nginx/files/scripts/backup.sh
. Definicję zasobu umieszczamy natomiast w osobnym pliku np. backup.pp
:
1 2 |
# cd /etc/puppetlabs/code/environments/production/modules/nginx # vim backup.pp |
Zawartość pliku backup.pp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
define nginx::backup( String $hour = '00', String $minute = '00', ) { file { '/scripts': ensure => directory, mode => '0755', owner => 'root', group => 'root', }-> file { "/scripts/${name}": source => "puppet:///modules/nginx/scripts/${name}", mode => '0755', }-> cron { "Run ${name}": command => "/scripts/${name}", hour => $hour, minute => $minute, } } |
Plik config.pp
modyfikujemy następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class nginx::config( Boolean $standard_port = $::nginx::standard_port, String $site_name = $::nginx::site_name, String $site_domain = $::nginx::site_domain, ){ file { '/etc/nginx/default.d/default': ensure => absent, }-> file { "/etc/nginx/default.d/${site_domain}": ensure => file, mode => '0600', owner => 'root', group => 'root', content => template('nginx/vhost.conf.erb'), } file { '/var/www': ensure => directory, mode => '0755', owner => 'root', group => 'root', } file { "/var/www/${site_name}": source => 'puppet:///modules/nginx/website', recurse => true, require => File['/var/www'], } nginx::backup { 'backup.sh': hour => '01', minute => '00', } } |
Po uruchomieniu na agencie:
1 |
# puppet agent -t |
Na agencie pojawi się owy skrypt /scripts/backup.sh
a na liście zadań crona pojawi się nowe zadanie:
1 2 3 4 |
# crontab -l # # Puppet Name: Run backup.sh 0 1 * * * /scripts/backup.sh |
Konfiguracja SSH (OLD).
Jeżeli zachodzi potrzeba modyfikacji konfiguracji SSH dla systemu to również można użyć Puppeta.
Tworzymy katalogi dla konfiguracji SSH:
1 2 |
# mkdir -p /puppet/modules/ssh/manifests # mkdir -p /puppet/modules/ssh/files |
Zakładamy plik /puppet/modules/ssh/manifests/init.pp
o treści:
1 2 3 4 5 6 7 8 9 10 11 12 |
# Manage the SSH service class ssh { service { 'ssh': ensure => running, } file { '/etc/ssh/sshd_config': source => 'puppet:///puppet/modules/ssh/sshd_config', notify => Service['ssh'], owner => 'root', group => 'root', } } |
Zakładamy plik /puppet/ modules/ssh/files/sshd_config
z zawartością:
1 2 3 4 5 6 |
Port 22 Protocol 2 PermitRootLogin no PasswordAuthentication no AllowUsers user1 user2 UsePAM yes |
Do definicji węzła server1 dodajemy wczytywanie nowej klasy ssh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
node 'server1' { include nginx include ssh user { 'user1': ensure => present, comment => 'First and Second Name', home => '/home/user1', managehome => true, } ssh_authorized_key { 'user1_ssh': user => 'user1', type => 'rsa', key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDozMFm++tr0UFUeHZTT3vqqygsnMcdkIiDDCYQz3140lOjx3kts4lPF41gVU1V6cVoY7yJ3XVMUguEHOEwoMuvUudSurN0ufIxh5H8ZVott27g7aJZGwUdEkdPNX/U1G+GnQ0dU/RtPw+oTIaXlnMEG6T9ECmZP8ON5n3uvlpsH/9t6U19B4t2/0oPIfu5H+5TgNDbweceun5X39XZm/PqMOy7A0Ynuh/4g9iA4VIo2YUrQV7kA2lK6tWQZ1SoJnrMr21NUq8L4fP1KclGcZz2dmRbDSDJNGLPlKjZtwXK+cLGQzvBUpqFjGG8FDx5Lz9AhxhVg/MiZfXNq1H0mw/n', } } |
Tak jak po każdej zmianie tak i teraz uruchamiamy ponownie papply:
1 |
# papply |