Puppet bir konfigürasyon yönetim sistemidir. Konakçıları istenilen duruma getirmek ve bu durumu korumak için kullanılır.
Beş yılı aşkın bir süredir Puppet'la çalışıyorum. Bu metin esasen resmi belgelerdeki önemli noktaların çevrilmiş ve yeniden düzenlenmiş bir derlemesidir ve yeni başlayanların Puppet'ın özünü hızlı bir şekilde anlamalarını sağlayacaktır.
Temel bilgiler
Puppet'ın işletim sistemi istemci-sunucudur, ancak aynı zamanda sınırlı işlevsellikle sunucusuz çalışmayı da destekler.
Çekme işlemi modeli kullanılır: Varsayılan olarak, her yarım saatte bir, istemciler bir yapılandırma için sunucuyla iletişime geçer ve bunu uygular. Ansible ile çalıştıysanız farklı bir push modeli kullanırlar: Yönetici yapılandırmayı uygulama sürecini başlatır, istemcilerin kendisi hiçbir şey uygulamaz.
Ağ iletişimi sırasında iki yönlü TLS şifrelemesi kullanılır: sunucu ve istemcinin kendi özel anahtarları ve ilgili sertifikaları vardır. Tipik olarak sunucu, istemciler için sertifikalar verir, ancak prensipte harici bir CA kullanmak mümkündür.
Manifestolara giriş
Kukla terminolojisinde kukla sunucusuna bağlamak düğümler (düğümler). Düğümlerin konfigürasyonu yazılmıştır manifestolarda özel bir programlama dilinde - Puppet DSL.
Puppet DSL bildirimsel bir dildir. Düğümün istenen durumunu bireysel kaynakların bildirimleri şeklinde açıklar, örneğin:
- Dosya mevcut ve belirli bir içeriğe sahip.
- Paket kurulur.
- Servis başladı.
Kaynaklar birbirine bağlanabilir:
- Bağımlılıklar vardır ve kaynakların kullanım sırasını etkilerler.
Örneğin, "önce paketi kurun, ardından yapılandırma dosyasını düzenleyin, ardından hizmeti başlatın." - Bildirimler var; bir kaynak değiştiyse, kendisine abone olan kaynaklara bildirim gönderir.
Örneğin, yapılandırma dosyası değişirse hizmeti otomatik olarak yeniden başlatabilirsiniz.
Ek olarak, Puppet DSL'de koşullu ifadeler ve seçicilerin yanı sıra işlevler ve değişkenler de bulunur. Çeşitli şablon oluşturma mekanizmaları da desteklenmektedir - EPP ve ERB.
Puppet Ruby'de yazılmıştır, pek çok yapı ve terim buradan alınmıştır. Ruby, Puppet'ı genişletmenize, karmaşık mantık, yeni kaynak türleri ve işlevler eklemenize olanak tanır.
Puppet çalışırken, sunucudaki her bir düğüme ilişkin bildirimler bir dizinde derlenir. Rehber fonksiyonların, değişkenlerin değeri ve koşullu ifadelerin genişletilmesi hesaplandıktan sonra kaynakların ve bunların ilişkilerinin bir listesidir.
Sözdizimi ve kod stili
Sağlanan örnekler yeterli değilse, sözdizimini anlamanıza yardımcı olacak resmi belgelerin bölümleri şunlardır:
İşte manifestin neye benzediğine dair bir örnek:
# Комментарии пишутся, как и много где, после решётки.
#
# Описание конфигурации ноды начинается с ключевого слова node,
# за которым следует селектор ноды — хостнейм (с доменом или без)
# или регулярное выражение для хостнеймов, или ключевое слово default.
#
# После этого в фигурных скобках описывается собственно конфигурация ноды.
#
# Одна и та же нода может попасть под несколько селекторов. Про приоритет
# селекторов написано в статье про синтаксис описания нод.
node 'hostname', 'f.q.d.n', /regexp/ {
# Конфигурация по сути является перечислением ресурсов и их параметров.
#
# У каждого ресурса есть тип и название.
#
# Внимание: не может быть двух ресурсов одного типа с одинаковыми названиями!
#
# Описание ресурса начинается с его типа. Тип пишется в нижнем регистре.
# Про разные типы ресурсов написано ниже.
#
# После типа в фигурных скобках пишется название ресурса, потом двоеточие,
# дальше идёт опциональное перечисление параметров ресурса и их значений.
# Значения параметров указываются через т.н. hash rocket (=>).
resource { 'title':
param1 => value1,
param2 => value2,
param3 => value3,
}
}
Girinti ve satır sonları bildirimin zorunlu bir parçası değildir, ancak önerilen bir bölüm vardır.
- İki boşluklu girintiler, sekmeler kullanılmaz.
- Kıvrımlı ayraçlar boşlukla ayrılır; iki nokta üst üste boşlukla ayrılmaz.
- Sonuncusu da dahil olmak üzere her parametreden sonra virgül. Her parametre ayrı bir satırdadır. Parametresiz ve tek parametreli durum için bir istisna yapılmıştır: tek satıra ve virgülsüz yazabilirsiniz (ör.
resource { 'title': }
иresource { 'title': param => value }
). - Parametrelerdeki oklar aynı seviyede olmalıdır.
- Kaynak ilişkisi okları önlerine yazılır.
Pappet sunucusundaki dosyaların konumu
Daha fazla açıklama için “kök dizin” kavramını tanıtacağım. Kök dizin, belirli bir düğüm için Puppet yapılandırmasını içeren dizindir.
Kök dizin, Puppet sürümüne ve kullanılan ortamlara bağlı olarak değişir. Ortamlar, ayrı dizinlerde depolanan bağımsız yapılandırma kümeleridir. Genellikle git ile birlikte kullanılır; bu durumda ortamlar git dallarından oluşturulur. Buna göre her düğüm bir veya başka bir ortamda bulunur. Bu, düğümün kendisinde veya bir sonraki makalede bahsedeceğim ENC'de yapılandırılabilir.
- Üçüncü versiyonda ("eski Kukla") temel dizin şuydu:
/etc/puppet
. Ortamların kullanımı isteğe bağlıdır; örneğin, bunları eski Puppet'ta kullanmıyoruz. Ortamlar kullanılıyorsa, bunlar genellikle/etc/puppet/environments
kök dizin ortam dizini olacaktır. Ortamlar kullanılmıyorsa kök dizin, temel dizin olacaktır. - Dördüncü versiyondan itibaren (“yeni Kukla”) ortamların kullanımı zorunlu hale geldi ve temel dizin şuraya taşındı:
/etc/puppetlabs/code
. Buna göre ortamlar depolanır./etc/puppetlabs/code/environments
, kök dizin ortam dizinidir.
Kök dizinde bir alt dizin bulunmalıdır manifests
Düğümleri açıklayan bir veya daha fazla bildirimi içeren. Ayrıca bir alt dizin bulunmalıdır. modules
modülleri içeren . Hangi modüllerin olduğunu biraz sonra anlatacağım. Ayrıca eski Puppet'ın bir alt dizini de olabilir files
Düğümlere kopyaladığımız çeşitli dosyaları içeren. Yeni Puppet'ta tüm dosyalar modüllere yerleştirilmiştir.
Manifest dosyaları şu uzantıya sahiptir: .pp
.
Birkaç savaş örneği
Düğümün ve üzerindeki kaynağın açıklaması
Düğümde server1.testdomain
bir dosya oluşturulmalıdır /etc/issue
içerikli Debian GNU/Linux n l
. Dosyanın sahibi bir kullanıcı ve gruba ait olmalıdır root
erişim hakları olmalıdır 644
.
Bir manifesto yazıyoruz:
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 в начале будет воспринято как записанное в восьмеричной системе, и всё пойдёт не так, как задумано
}
}
Bir düğümdeki kaynaklar arasındaki ilişkiler
Düğümde server2.testdomain
nginx'in önceden hazırlanmış bir konfigürasyonla çalışıyor olması gerekir.
Sorunu ayrıştıralım:
- Paketin kurulması gerekiyor
nginx
. - Yapılandırma dosyalarının sunucudan kopyalanması gereklidir.
- Hizmetin çalışıyor olması gerekiyor
nginx
. - Yapılandırma güncellenirse hizmetin yeniden başlatılması gerekir.
Bir manifesto yazıyoruz:
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 получает уведомление,
# соответствующий сервис перезапускается.
}
Bunun çalışması için kukla sunucusunda yaklaşık olarak aşağıdaki dosya konumuna ihtiyacınız vardır:
/etc/puppetlabs/code/environments/production/ # (это для нового Паппета, для старого корневой директорией будет /etc/puppet)
├── manifests/
│ └── site.pp
└── modules/
└── example/
└── files/
└── nginx-conf/
├── nginx.conf
├── mime.types
└── conf.d/
└── some.conf
Kaynak Türleri
Desteklenen kaynak türlerinin tam listesini burada bulabilirsiniz
dosya
Dosyaları, dizinleri, sembolik bağlantıları, içeriklerini ve erişim haklarını yönetir.
Parametreler:
- kaynak adı — dosyanın yolu (isteğe bağlı)
- yol — dosyanın yolu (adında belirtilmemişse)
- sağlamak - dosya tipi:
absent
- bir dosyayı silpresent
— herhangi bir türde bir dosya bulunmalıdır (dosya yoksa normal bir dosya oluşturulacaktır)file
- normal dosyadirectory
- dizinlink
- sembolik bağlantı
- içerik — dosya içerikleri (yalnızca normal dosyalar için uygundur; kaynak veya hedef)
- kaynak — dosyanın içeriğini kopyalamak istediğiniz yola bir bağlantı (ile birlikte kullanılamaz) içerik veya hedef). Şemalı bir URI olarak belirtilebilir
puppet:
(daha sonra kukla sunucudaki dosyalar kullanılacaktır) ve şemaylahttp:
(Umarım bu durumda ne olacağı açıktır) ve hatta diyagramlafile:
veya şemasız mutlak bir yol olarak (daha sonra düğümdeki yerel FS'deki dosya kullanılacaktır) - hedef - sembolik bağlantının işaret etmesi gereken yer (ile birlikte kullanılamaz) içerik veya kaynak)
- sahip — dosyanın sahibi olması gereken kullanıcı
- Grup — dosyanın ait olması gereken grup
- kip — dosya izinleri (dize olarak)
- recurse - özyinelemeli dizin işlemeyi etkinleştirir
- tasfiye - Puppet'ta açıklanmayan dosyaların silinmesini sağlar
- anabolik etkileri de mevcuttur - Puppet'ta açıklanmayan dizinlerin silinmesini sağlar
paket
Paketleri yükler ve kaldırır. Bildirimleri işleyebilir - parametre belirtilirse paketi yeniden yükler yeniden yükleme_on_refresh.
Parametreler:
- kaynak adı — paket adı (isteğe bağlı)
- isim — paket adı (adda belirtilmemişse)
- sağlayan — kullanılacak paket yöneticisi
- sağlamak — paketin istenen durumu:
present
,installed
- yüklü herhangi bir sürümlatest
- en son sürüm yüklüabsent
- silindi (apt-get remove
)purged
— yapılandırma dosyalarıyla birlikte silindi (apt-get purge
)held
- paket sürümü kilitli (apt-mark hold
)любая другая строка
— belirtilen sürüm yüklü
- yeniden yükleme_on_refresh - Eğer bir
true
, ardından bildirim alındıktan sonra paket yeniden yüklenecektir. Derleme parametrelerini değiştirirken paketlerin yeniden oluşturulmasının gerekli olabileceği kaynak tabanlı dağıtımlar için kullanışlıdır. Varsayılanfalse
.
hizmet
Hizmetleri yönetir. Bildirimleri işleyebilir - hizmeti yeniden başlatır.
Parametreler:
- kaynak adı — yönetilecek hizmet (isteğe bağlı)
- isim — yönetilmesi gereken hizmet (adında belirtilmemişse)
- sağlamak - hizmetin istenen durumu:
running
- başlatıldıstopped
- durduruldu
- etkinleştirmek — hizmeti başlatma yeteneğini kontrol eder:
true
— otomatik çalıştırma etkinleştirildi (systemctl enable
)mask
- gizlenmiş (systemctl mask
)false
— otomatik çalıştırma devre dışı bırakıldı (systemctl disable
)
- yeniden - hizmeti yeniden başlatma komutu
- durum — hizmet durumunu kontrol etme komutu
- yeniden başlatıldı — hizmet başlangıç betiğinin yeniden başlatmayı destekleyip desteklemediğini belirtin. Eğer
false
ve parametre belirtildi yeniden — bu parametrenin değeri kullanılır. Eğerfalse
ve parametre yeniden belirtilmemiş - hizmet durduruldu ve yeniden başlatılmaya başlandı (ancak systemd şu komutu kullanıyor:systemctl restart
). - durumu — hizmet başlangıç betiğinin komutu destekleyip desteklemediğini belirtin
status
. Eğerfalse
, daha sonra parametre değeri kullanılır durum. Varsayılantrue
.
exec
Harici komutları çalıştırır. Parametre belirtmezseniz oluşturur, Yalnızca, olmadıkça veya yalnızca yenileme, komut Puppet'in her çalıştırılışında çalıştırılacaktır. Bildirimleri işleyebilir - bir komutu çalıştırır.
Parametreler:
- kaynak adı - yürütülecek komut (isteğe bağlı)
- komuta — yürütülecek komut (adında belirtilmemişse)
- yol — yürütülebilir dosyanın aranacağı yollar
- Yalnızca — bu parametrede belirtilen komut sıfıra dönüş koduyla tamamlanırsa ana komut yürütülür
- olmadıkça — bu parametrede belirtilen komut sıfırdan farklı bir dönüş koduyla tamamlanırsa ana komut yürütülür
- oluşturur — bu parametrede belirtilen dosya mevcut değilse ana komut yürütülür
- yalnızca yenileme - Eğer bir
true
, bu durumda komut yalnızca bu yönetici diğer kaynaklardan bildirim aldığında çalıştırılacaktır. - cwd — komutun çalıştırılacağı dizin
- kullanıcı — komutun çalıştırılacağı kullanıcı
- sağlayan - komutun nasıl çalıştırılacağı:
- posix — bir alt süreç basitçe oluşturulur, belirttiğinizden emin olun yol
- kabuk - komut kabukta başlatılır
/bin/sh
, belirtilmemiş olabilir yol, küreselleştirmeyi, boruları ve diğer kabuk özelliklerini kullanabilirsiniz. Herhangi bir özel karakter varsa genellikle otomatik olarak algılanır (|
,;
,&&
,||
vb).
cron
Cronjobs'ı kontrol eder.
Parametreler:
- kaynak adı - sadece bir çeşit tanımlayıcı
- sağlamak — taç işi durumu:
present
- mevcut değilse oluşturabsent
- varsa sil
- komuta - hangi komutun çalıştırılacağı
- çevre - komutun hangi ortamda çalıştırılacağı (ortam değişkenlerinin listesi ve bunların değerleri
=
) - kullanıcı — komutun hangi kullanıcıdan çalıştırılacağı
- dakika, saat, iş günü, ay, ay günü — cron'un ne zaman çalıştırılacağı. Bu özelliklerden herhangi biri belirtilmezse crontab'daki değeri şu şekilde olacaktır:
*
.
Kukla 6.0'da cron sanki
Genel olarak kaynaklar hakkında
Kaynak benzersizliği için gereksinimler
Karşılaştığımız en yaygın hata Yinelenen beyan. Bu hata, dizinde aynı türde ve aynı adda iki veya daha fazla kaynak göründüğünde ortaya çıkar.
Bu nedenle tekrar yazacağım: Aynı düğüme ait bildirimler, aynı başlığa sahip, aynı türden kaynaklar içermemelidir!
Bazen aynı adı taşıyan ancak farklı paket yöneticilerine sahip paketleri kurmanız gerekebilir. Bu durumda parametreyi kullanmanız gerekir. name
hatayı önlemek için:
package { 'ruby-mysql':
ensure => installed,
name => 'mysql',
provider => 'gem',
}
package { 'python-mysql':
ensure => installed,
name => 'mysql',
provider => 'pip',
}
Diğer kaynak türleri, kopyaların önlenmesine yardımcı olmak için benzer seçeneklere sahiptir - name
у hizmet, command
у exec, ve benzeri.
Metaparametreler
Her kaynak türünün, niteliğine bakılmaksızın bazı özel parametreleri vardır.
Meta parametrelerin tam listesi
Kısa liste:
- gerektirir — bu parametre, bu kaynağın hangi kaynaklara bağlı olduğunu gösterir.
- önce - Bu parametre hangi kaynakların bu kaynağa bağlı olduğunu belirtir.
- abone ol — bu parametre, bu kaynağın hangi kaynaklardan bildirim aldığını belirtir.
- bildirmek — Bu parametre, hangi kaynakların bu kaynaktan bildirim alacağını belirtir.
Listelenen meta parametrelerin tümü, tek bir kaynak bağlantısını veya köşeli parantez içindeki bir dizi bağlantıyı kabul eder.
Kaynaklara bağlantılar
Kaynak bağlantısı yalnızca kaynağın belirtilmesidir. Esas olarak bağımlılıkları belirtmek için kullanılırlar. Var olmayan bir kaynağa başvurulması derleme hatasına neden olur.
Bağlantının sözdizimi şu şekildedir: büyük harfle kaynak türü (tür adı çift iki nokta üst üste içeriyorsa, adın iki nokta üst üste arasındaki her kısmı büyük harfle yazılır), ardından köşeli parantez içindeki kaynak adı (adın durumu) değişmez!). Boşluk bırakılmamalı, tip adından hemen sonra köşeli parantez yazılmalıdır.
Örnek:
file { '/file1': ensure => present }
file { '/file2':
ensure => directory,
before => File['/file1'],
}
file { '/file3': ensure => absent }
File['/file1'] -> File['/file3']
Bağımlılıklar ve bildirimler
Daha önce de belirtildiği gibi kaynaklar arasındaki basit bağımlılıklar geçişlidir. Bu arada, bağımlılık eklerken dikkatli olun; döngüsel bağımlılıklar oluşturabilirsiniz, bu da derleme hatasına neden olur.
Bağımlılıklardan farklı olarak bildirimler geçişli değildir. Bildirimler için aşağıdaki kurallar geçerlidir:
- Kaynak bir bildirim alırsa güncellenir. Güncelleme eylemleri kaynak türüne bağlıdır – exec komutu çalıştırır, hizmet hizmeti yeniden başlatır, paket paketi yeniden yükler. Kaynağın tanımlanmış bir güncelleme eylemi yoksa hiçbir şey olmaz.
- Puppet'ın bir çalışması sırasında kaynak bir defadan fazla güncellenmez. Bildirimlerin bağımlılıkları içermesi ve bağımlılık grafiğinin döngü içermemesi nedeniyle bu mümkündür.
- Puppet bir kaynağın durumunu değiştirirse kaynak, kendisine abone olan tüm kaynaklara bildirim gönderir.
- Bir kaynak güncellendiğinde kendisine abone olan tüm kaynaklara bildirim gönderir.
Belirtilmemiş parametrelerin işlenmesi
Kural olarak, bazı kaynak parametrelerinin varsayılan değeri yoksa ve bu parametre bildirimde belirtilmemişse Puppet, düğümdeki karşılık gelen kaynak için bu özelliği değiştirmez. Örneğin, eğer türde bir kaynak varsa dosya parametre belirtilmedi owner
, bu durumda Puppet ilgili dosyanın sahibini değiştirmez.
Sınıflara, değişkenlere ve tanımlara giriş
Diyelim ki konfigürasyonun aynı kısmına sahip birkaç düğümümüz var, ancak farklılıklar da var - aksi takdirde hepsini tek bir blokta tanımlayabilirdik node {}
. Tabii ki, konfigürasyonun aynı kısımlarını basitçe kopyalayabilirsiniz, ancak genel olarak bu kötü bir çözümdür - konfigürasyon büyür ve konfigürasyonun genel bölümünü değiştirirseniz, aynı şeyi birçok yerde düzenlemeniz gerekecektir. Aynı zamanda hata yapmak kolaydır ve genel olarak DRY (kendini tekrar etme) ilkesi bir nedenden dolayı icat edilmiştir.
Bu sorunu çözmek için şöyle bir tasarım var: sınıf.
Sınıflar
Öncelikle sınıfın tanımlanması gerekiyor. Açıklamanın kendisi hiçbir yere herhangi bir kaynak eklemez. Sınıf manifestlerde açıklanmıştır:
# Описание класса начинается с ключевого слова class и его названия.
# Дальше идёт тело класса в фигурных скобках.
class example_class {
...
}
Bundan sonra sınıf kullanılabilir:
# первый вариант использования — в стиле ресурса с типом class
class { 'example_class': }
# второй вариант использования — с помощью функции include
include example_class
# про отличие этих двух вариантов будет рассказано дальше
Önceki görevden bir örnek; nginx'in kurulumunu ve yapılandırmasını bir sınıfa taşıyalım:
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
}
Değişkenler
Önceki örnekteki sınıf hiç esnek değil çünkü her zaman aynı nginx yapılandırmasını getiriyor. Yapılandırma değişkenine giden yolu oluşturalım, ardından bu sınıf herhangi bir yapılandırmayla nginx'i yüklemek için kullanılabilir.
Yapılabilir
Dikkat: Puppet'taki değişkenler değişmezdir!
Ayrıca bir değişkene ancak bildirildikten sonra erişilebilir, aksi halde değişkenin değeri şu şekilde olacaktır: undef
.
Değişkenlerle çalışma örneği:
# создание переменных
$variable = 'value'
$var2 = 1
$var3 = true
$var4 = undef
# использование переменных
$var5 = $var6
file { '/tmp/text': content => $variable }
# интерполяция переменных — раскрытие значения переменных в строках. Работает только в двойных кавычках!
$var6 = "Variable with name variable has value ${variable}"
Kukla var ad alanlarıve değişkenler buna göre görünürlük alanı: Aynı isimde bir değişken farklı isim alanlarında tanımlanabilir. Bir değişkenin değeri çözümlenirken, değişken geçerli ad alanında, ardından onu çevreleyen ad alanında vb. aranır.
Ad alanı örnekleri:
- global - sınıf veya düğüm tanımının dışındaki değişkenler oraya gider;
- düğüm açıklamasındaki düğüm ad alanı;
- sınıf açıklamasındaki sınıf ad alanı.
Bir değişkene erişirken belirsizliği önlemek için değişken adında ad alanını belirtebilirsiniz:
# переменная без пространства имён
$var
# переменная в глобальном пространстве имён
$::var
# переменная в пространстве имён класса
$classname::var
$::classname::var
Nginx yapılandırmasına giden yolun değişkende olduğu konusunda hemfikir olalım $nginx_conf_source
. Daha sonra sınıf şöyle görünecek:
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
}
Ancak verilen örnek kötü çünkü sınıfın içinde bir yerde falan isimle bir değişkenin kullanıldığına dair bazı "gizli bilgiler" var. Bu bilgiyi genelleştirmek çok daha doğrudur - sınıfların parametreleri olabilir.
Sınıf parametreleri sınıf ad alanındaki değişkenlerdir, sınıf başlığında belirtilirler ve sınıf gövdesindeki normal değişkenler gibi kullanılabilirler. Manifestte sınıf kullanılırken parametre değerleri belirtilir.
Parametre varsayılan değere ayarlanabilir. Bir parametrenin varsayılan değeri yoksa ve kullanıldığında değer ayarlanmamışsa derleme hatasına neden olur.
Yukarıdaki örnekten sınıfı parametreleştirelim ve iki parametre ekleyelim: ilki, gerekli olan, yapılandırmanın yoludur ve ikincisi, isteğe bağlı, nginx'li paketin adıdır (örneğin, Debian'da paketler vardır). 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'ta değişkenler yazılır. Yemek yemek
Tür, parametre adından hemen önce yazılır:
class example (
String $param1,
Integer $param2,
Array $param3,
Hash $param4,
Hash[String, String] $param5,
) {
...
}
Sınıflar: sınıf adı vs sınıf{'sınıf adı':} dahil
Her sınıf bir tür kaynaktır sınıf. Diğer herhangi bir kaynak türünde olduğu gibi, aynı düğümde aynı sınıfın iki örneği bulunamaz.
Aynı düğüme iki kez sınıf eklemeye çalışırsanız class { 'classname':}
(fark yok, farklı veya aynı parametrelerde), derleme hatası oluşacaktır. Ancak kaynak stilinde bir sınıf kullanırsanız, onun tüm parametrelerini manifestte hemen açıkça ayarlayabilirsiniz.
Ancak eğer kullanırsanız include
, daha sonra sınıf istenildiği kadar eklenebilir. Gerçek şu ki include
dizine bir sınıfın eklenip eklenmediğini kontrol eden önemsiz bir işlevdir. Sınıf dizinde değilse ekler, zaten varsa hiçbir şey yapmaz. Ama kullanılması durumunda include
Sınıf bildirimi sırasında sınıf parametrelerini ayarlayamazsınız - gerekli tüm parametrelerin harici bir veri kaynağında (Hiera veya ENC) ayarlanması gerekir. Bir sonraki yazımızda bunlardan bahsedeceğiz.
Tanımlar
Önceki blokta da söylendiği gibi aynı sınıf bir düğümde birden fazla bulunamaz. Ancak bazı durumlarda aynı kod bloğunu aynı düğümde farklı parametrelerle kullanabilmeniz gerekir. Yani kendine ait bir kaynak türüne ihtiyaç var.
Örneğin PHP modülünü kurmak için Avito'da aşağıdakileri yapıyoruz:
- Paketi bu modülle yükleyin.
- Bu modül için bir konfigürasyon dosyası oluşturalım.
- Php-fpm için yapılandırmaya bir sembolik bağlantı oluşturuyoruz.
- Php cli için yapılandırmaya bir sembolik bağlantı oluşturuyoruz.
Bu gibi durumlarda aşağıdaki gibi bir tasarım $title
, bildirildiğinde kaynak adının gideceği yer. Tıpkı sınıflarda olduğu gibi, önce bir tanımın tanımlanması gerekir, daha sonra kullanılabilir.
PHP için bir modül içeren basitleştirilmiş bir örnek:
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' }
}
Yinelenen bildirim hatasını yakalamanın en kolay yolu Tanımla'dır. Bu, bir tanımın sabit ada sahip bir kaynağı varsa ve bazı düğümlerde bu tanımın iki veya daha fazla örneği varsa meydana gelir.
Kendinizi bundan korumak kolaydır: Tanımın içindeki tüm kaynakların, bağlı olarak bir adı olmalıdır. $title
. Bir alternatif, kaynakların önemsiz olarak eklenmesidir; en basit durumda, tanımın tüm örneklerinde ortak olan kaynakları ayrı bir sınıfa taşımak ve bu sınıfı tanım - fonksiyona dahil etmek yeterlidir. include
etkisiz.
Kaynak eklerken bağımsızlığa ulaşmanın başka yolları da vardır, yani işlevleri kullanmak defined
и ensure_resources
ama bunu bir sonraki bölümde anlatacağım.
Sınıflar ve tanımlar için bağımlılıklar ve bildirimler
Sınıflar ve tanımlar, bağımlılıkların ve bildirimlerin işlenmesine aşağıdaki kuralları ekler:
- bir sınıfa/tanıma bağımlılık, sınıfın/tanımın tüm kaynaklarına bağımlılıklar ekler;
- bir sınıf/tanımlama bağımlılığı, tüm sınıf/tanımlama kaynaklarına bağımlılıklar ekler;
- sınıf/tanımlama bildirimi, sınıfın/tanımlamanın tüm kaynaklarını bilgilendirir;
- class/define aboneliği, class/define'in tüm kaynaklarına abone olur.
Koşullu ifadeler ve seçiciler
if
Burada her şey basit:
if ВЫРАЖЕНИЕ1 {
...
} elsif ВЫРАЖЕНИЕ2 {
...
} else {
...
}
olmadıkça
ters bir if olmadığı sürece: ifade yanlışsa kod bloğu yürütülür.
unless ВЫРАЖЕНИЕ {
...
}
dava
Burada da karmaşık bir şey yok. Değer olarak normal değerleri (dizeler, sayılar vb.), düzenli ifadeleri ve veri türlerini kullanabilirsiniz.
case ВЫРАЖЕНИЕ {
ЗНАЧЕНИЕ1: { ... }
ЗНАЧЕНИЕ2, ЗНАЧЕНИЕ3: { ... }
default: { ... }
}
Seçiciler
Seçici, aşağıdakine benzer bir dil yapısıdır: case
ancak bir kod bloğunu yürütmek yerine bir değer döndürür.
$var = $othervar ? { 'val1' => 1, 'val2' => 2, default => 3 }
Модули
Konfigürasyon küçük olduğunda kolaylıkla tek bir bildirimde tutulabilir. Ancak ne kadar çok konfigürasyon tanımlarsak, bildirimde o kadar çok sınıf ve düğüm bulunur, büyür ve birlikte çalışmak elverişsiz hale gelir.
Ek olarak, kodun yeniden kullanımı sorunu da vardır; kodun tamamı tek bir bildirimde olduğunda, bu kodu başkalarıyla paylaşmak zordur. Bu iki sorunu çözmek için Puppet'ın modül adı verilen bir varlığı vardır.
Модули - bunlar ayrı bir dizine yerleştirilen sınıflar, tanımlar ve diğer Puppet varlıkları kümeleridir. Başka bir deyişle, bir modül Puppet mantığının bağımsız bir parçasıdır. Örneğin, nginx ile çalışmak için bir modül olabilir ve yalnızca nginx ile çalışmak için gerekenleri içerecektir veya PHP ile çalışmak için bir modül vb. olabilir.
Modüller sürümlendirilmiştir ve modüllerin birbirine bağımlılığı da desteklenir. Açık bir modül deposu var -
Kukla sunucusunda modüller, kök dizinin modüller alt dizininde bulunur. Her modülün içinde standart bir dizin şeması vardır - bildirimler, dosyalar, şablonlar, lib vb.
Bir modüldeki dosya yapısı
Modülün kökü, açıklayıcı adlara sahip aşağıdaki dizinleri içerebilir:
manifests
- manifestolar içeriyorfiles
- dosyalar içeriyortemplates
- şablonlar içerirlib
— Ruby kodunu içerir
Bu, dizinlerin ve dosyaların tam listesi değil, ancak şimdilik bu makale için yeterli.
Kaynak adları ve modüldeki dosyaların adları
Bir modüldeki kaynaklar (sınıflar, tanımlar) istediğiniz gibi adlandırılamaz. Ek olarak, bir kaynağın adı ile Puppet'in o kaynağın açıklamasını arayacağı dosyanın adı arasında doğrudan bir ilişki vardır. Adlandırma kurallarını ihlal ederseniz Puppet kaynak açıklamasını bulamaz ve bir derleme hatası alırsınız.
Kurallar basit:
- Bir modüldeki tüm kaynakların modül ad alanında olması gerekir. Modül çağrılırsa
foo
, o zaman içindeki tüm kaynaklar adlandırılmalıdırfoo::<anything>
ya da sadecefoo
. - Dosyada modül adını taşıyan bir kaynak bulunmalıdır
init.pp
. - Diğer kaynaklar için dosya adlandırma şeması aşağıdaki gibidir:
- modül adını taşıyan önek atılır
- varsa tüm çift iki nokta üst üste eğik çizgilerle değiştirilir
- uzantı eklendi
.pp
Bir örnekle göstereceğim. Diyelim ki bir modül yazıyorum nginx
. Aşağıdaki kaynakları içerir:
- sınıf
nginx
manifestte açıklananinit.pp
; - sınıf
nginx::service
manifestte açıklananservice.pp
; - tanımlamak
nginx::server
manifestte açıklananserver.pp
; - tanımlamak
nginx::server::location
manifestte açıklananserver/location.pp
.
Şablonları
Elbette şablonların ne olduğunu kendiniz biliyorsunuz; bunları burada ayrıntılı olarak açıklamayacağım. Ama her ihtimale karşı bırakacağım
Şablonlar nasıl kullanılır: Bir şablonun anlamı bir işlev kullanılarak genişletilebilir template
, şablonun yolunu iletir. Türdeki kaynaklar için dosya parametre ile birlikte kullanılır content
. Örneğin şöyle:
file { '/tmp/example': content => template('modulename/templatename.erb')
Yolu görüntüle <modulename>/<filename>
dosyayı ima eder <rootdir>/modules/<modulename>/templates/<filename>
.
Ayrıca bir işlevi var inline_template
— giriş olarak dosya adını değil şablon metnini alır.
Şablonların içinde geçerli kapsamdaki tüm Puppet değişkenlerini kullanabilirsiniz.
Puppet, ERB ve EPP formatındaki şablonları destekler:
Kısaca ERB hakkında
Kontrol Yapıları:
<%= ВЫРАЖЕНИЕ %>
— ifadenin değerini girin<% ВЫРАЖЕНИЕ %>
— bir ifadenin değerini hesaplayın (eklemeden). Koşullu ifadeler (if) ve döngüler (her biri) genellikle buraya gelir.<%# КОММЕНТАРИЙ %>
ERB'deki ifadeler Ruby'de yazılmıştır (ERB aslında Gömülü Ruby'dir).
Bildirimden değişkenlere erişmek için şunu eklemeniz gerekir: @
değişken adına. Bir kontrol yapısından sonra görünen satır sonunu kaldırmak için bir kapanış etiketi kullanmanız gerekir. -%>
.
Şablonu kullanma örneği
Diyelim ki ZooKeeper'ı kontrol etmek için bir modül yazıyorum. Yapılandırmayı oluşturmaktan sorumlu sınıf şuna benzer:
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'),
}
}
Ve ilgili şablon zoo.cfg.erb
- Bu yüzden:
<% 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 -%>
Gerçekler ve Yerleşik Değişkenler
Genellikle yapılandırmanın belirli bir kısmı, düğümde o anda olup bitenlere bağlıdır. Örneğin, Debian sürümünün ne olduğuna bağlı olarak paketin bir veya başka bir sürümünü kurmanız gerekir. Tüm bunları manuel olarak izleyebilir, düğümler değişirse bildirimleri yeniden yazabilirsiniz. Ancak bu ciddi bir yaklaşım değil; otomasyon çok daha iyi.
Düğümler hakkında bilgi edinmek için Puppet'ın gerçekler adı verilen bir mekanizması vardır. Gerçekler - bu, genel ad alanındaki sıradan değişkenler biçiminde bildirimlerde bulunan, düğümle ilgili bilgidir. Örneğin, ana bilgisayar adı, işletim sistemi sürümü, işlemci mimarisi, kullanıcı listesi, ağ arayüzleri listesi ve adresleri ve çok daha fazlası. Gerçekler manifestlerde ve şablonlarda normal değişkenler olarak mevcuttur.
Gerçeklerle çalışmaya bir örnek:
notify { "Running OS ${facts['os']['name']} version ${facts['os']['release']['full']}": }
# ресурс типа notify просто выводит сообщение в лог
Resmi olarak konuşursak, bir olgunun bir adı (dizge) ve bir değeri vardır (çeşitli türleri mevcuttur: dizeler, diziler, sözlükler). Yemek yemek
Operasyon sırasında, kukla ajan ilk olarak mevcut tüm bilgi toplayıcıları kağıt sunucusundan düğüme kopyalar, ardından bunları başlatır ve toplanan gerçekleri sunucuya gönderir; Bundan sonra sunucu kataloğu derlemeye başlar.
Yürütülebilir dosyalar biçimindeki gerçekler
Bu tür gerçekler dizindeki modüllere yerleştirilir facts.d
. Tabii ki, dosyalar çalıştırılabilir olmalıdır. Çalıştırıldığında, bilgileri standart çıktıya YAML veya anahtar=değer biçiminde çıkarmaları gerekir.
Gerçeklerin, modülünüzün konuşlandırıldığı poppet sunucusu tarafından kontrol edilen tüm düğümler için geçerli olduğunu unutmayın. Bu nedenle, komut dosyasında, sisteminizin çalışması için gerekli tüm programların ve dosyaların bulunduğunu kontrol etmeye dikkat edin.
#!/bin/sh
echo "testfact=success"
#!/bin/sh
echo '{"testyamlfact":"success"}'
Yakut gerçekleri
Bu tür gerçekler dizindeki modüllere yerleştirilir 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
Metin gerçekleri
Bu tür gerçekler dizindeki düğümlere yerleştirilir /etc/facter/facts.d
eski Puppet'ta veya /etc/puppetlabs/facts.d
yeni Puppet'ta.
examplefact=examplevalue
---
examplefact2: examplevalue2
anotherfact: anothervalue
Gerçeklere Ulaşmak
Gerçeklere yaklaşmanın iki yolu vardır:
- sözlük aracılığıyla
$facts
:$facts['fqdn']
; - değişken adı olarak olgu adını kullanarak:
$fqdn
.
Sözlük kullanmak en iyisidir $facts
veya daha iyisi global ad alanını belirtin ($::facts
).
Yerleşik Değişkenler
Gerçeklerin yanı sıra bir de var
- güvenilir gerçekler — müşterinin sertifikasından alınan değişkenler (sertifika genellikle bir poppet sunucusunda verildiğinden, aracı sadece sertifikasını alıp değiştiremez, bu nedenle değişkenler "güvenilir"): sertifikanın adı, sertifikanın adı ana bilgisayar ve etki alanı, sertifikadaki uzantılar.
- sunucu gerçekleri —sunucu hakkındaki bilgilerle ilgili değişkenler—sürüm, ad, sunucu IP adresi, ortam.
- ajan gerçekleri — gerçekte değil, doğrudan kukla aracısı tarafından eklenen değişkenler — sertifika adı, aracı sürümü, kukla sürümü.
- ana değişkenler - Pappetmaster değişkenleri (aynen böyle!). Bu yaklaşık olarak aynı sunucu gerçekleriartı konfigürasyon parametre değerleri mevcuttur.
- derleyici değişkenleri — her kapsamda farklılık gösteren derleyici değişkenleri: geçerli modülün adı ve geçerli nesneye erişilen modülün adı. Örneğin özel sınıflarınızın doğrudan diğer modüllerden kullanılmadığını kontrol etmek için kullanılabilirler.
Ek 1: Tüm bunlar nasıl çalıştırılır ve hata ayıklanır?
Makale birçok kukla kod örneği içeriyordu, ancak bize bu kodun nasıl çalıştırılacağından hiç bahsetmedi. Neyse kendimi düzeltiyorum.
Puppet'ı çalıştırmak için bir aracı yeterlidir ancak çoğu durumda bir sunucuya da ihtiyacınız olacaktır.
ajan
En azından sürüm 5'ten bu yana, kukla ajan paketleri
En basit durumda, kukla yapılandırmasını kullanmak için aracıyı sunucusuz modda başlatmak yeterlidir: kukla kodunun düğüme kopyalanması koşuluyla, aracıyı başlatın. 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
Tabii ki, sunucuyu kurmak ve aracıları daemon modunda düğümlerde çalıştırmak daha iyidir - daha sonra her yarım saatte bir, sunucudan indirilen yapılandırmayı uygulayacaklar.
İtme işi modelini taklit edebilirsiniz - ilgilendiğiniz düğüme gidin ve başlayın sudo puppet agent -t
. Anahtar -t
(--test
) aslında ayrı ayrı etkinleştirilebilecek çeşitli seçenekler içerir. Bu seçenekler aşağıdakileri içerir:
- daemon modunda çalıştırmayın (ajan varsayılan olarak daemon modunda başlar);
- kataloğu uyguladıktan sonra kapatma (varsayılan olarak aracı her yarım saatte bir çalışmaya ve yapılandırmayı uygulamaya devam edecektir);
- ayrıntılı bir çalışma günlüğü yazın;
- Dosyalardaki değişiklikleri göster.
Aracının değişiklik olmayan bir çalışma modu vardır - doğru konfigürasyonu yazdığınızdan emin olmadığınızda ve aracının çalışma sırasında tam olarak neyi değiştireceğini kontrol etmek istediğinizde bunu kullanabilirsiniz. Bu mod parametre tarafından etkinleştirilir --noop
komut satırında: sudo puppet agent -t --noop
.
Ek olarak, işin hata ayıklama günlüğünü de etkinleştirebilirsiniz - burada kukla gerçekleştirdiği tüm eylemler hakkında yazar: şu anda işlediği kaynak hakkında, bu kaynağın parametreleri hakkında, hangi programları başlattığı hakkında. Tabii bu bir parametre --debug
.
Sunucu
Bu makalede ppet sunucusunun tam kurulumunu ve ona kod dağıtmayı ele almayacağım; yalnızca az sayıda sunucuyla çalışmak için ek yapılandırma gerektirmeyen, kutudan çıktığı haliyle sunucunun tamamen işlevsel bir sürümünün bulunduğunu söyleyeceğim. düğümler (örneğin yüze kadar). Daha fazla sayıda düğümün ayarlanması gerekecektir - varsayılan olarak kukla sunucusu dörtten fazla çalışanı başlatmaz, daha yüksek performans için sayılarını artırmanız gerekir ve bellek sınırlarını artırmayı unutmayın, aksi takdirde sunucu çoğu zaman çöp toplama işlemi yapar.
Kod dağıtımı - hızlı ve kolay bir şekilde ihtiyacınız varsa (r10k'ye)[
Ek 2: Kodlama Yönergeleri
- Tüm mantığı sınıflara ve tanımlara yerleştirin.
- Sınıfları ve tanımları, düğümleri açıklayan bildirimlerde değil, modüllerde tutun.
- Gerçekleri kullanın.
- Ana makine adlarına göre if'ler yapmayın.
- Sınıflar ve tanımlar için parametreler eklemekten çekinmeyin; bu, sınıf/tanım gövdesinde gizli olan örtülü mantıktan daha iyidir.
Bunu neden yapmanızı önerdiğimi bir sonraki makalede açıklayacağım.
Sonuç
Girişle bitirelim. Bir sonraki yazımda sizlere Hiera, ENC ve PuppetDB'den bahsedeceğim.
Ankete sadece kayıtlı kullanıcılar katılabilir.
Aslında çok daha fazla materyal var - Aşağıdaki konularda makaleler yazabilirim, okumak isteyebileceğiniz konulara oy verebilirim:
- %59,1Gelişmiş kukla yapıları - bazı sonraki seviye saçmalıklar: döngüler, haritalama ve diğer lambda ifadeleri, kaynak toplayıcılar, dışa aktarılan kaynaklar ve Puppet, etiketler, sağlayıcılar, soyut veri türleri aracılığıyla ana bilgisayarlar arası iletişim.13
- %31,8“Ben annemin yöneticisiyim” ya da Avito'da farklı versiyonlardaki birkaç poppet sunucusuyla nasıl arkadaşlık kurduğumuzu ve prensip olarak poppet sunucusunu yönetmeyle ilgili kısmı.7
- %81,8Kukla kodunu nasıl yazıyoruz: enstrümantasyon, dokümantasyon, test etme, CI/CD.18
22 kullanıcı oy kullandı. 9 kişi çekimser kaldı.
Kaynak: habr.com