パペットの紹介

Puppet は構成管理システムです。 これは、ホストを望ましい状態にし、その状態を維持するために使用されます。

私は Puppet を使って XNUMX 年以上働いています。 このテキストは基本的に、公式ドキュメントの重要なポイントを翻訳して再配列したもので、初心者が Puppet の本質をすぐに理解できるようにします。

パペットの紹介

基本情報

Puppet のオペレーティング システムはクライアント/サーバーですが、機能が限定されたサーバーレス操作もサポートしています。

操作にはプル モデルが使用されます。デフォルトでは、クライアントは XNUMX 分に XNUMX 回、サーバーに接続して構成を取得し、それを適用します。 Ansible を使用したことがある場合は、別のプッシュ モデルが使用されます。管理者が設定を適用するプロセスを開始し、クライアント自体は何も適用しません。

ネットワーク通信中、双方向 TLS 暗号化が使用されます。サーバーとクライアントは独自の秘密キーと対応する証明書を持ちます。 通常、サーバーはクライアントに証明書を発行しますが、原則として外部 CA を使用することも可能です。

マニフェストの紹介

パペット用語で言うと パペットサーバーへ 接続する ノード (ノード)。 ノードの設定が書き込まれます マニフェストで 特別なプログラミング言語 - Puppet DSL で。

Puppet DSL は宣言型言語です。 これは、ノードの望ましい状態を個々のリソースの宣言の形式で記述します。次に例を示します。

  • ファイルは存在し、特定の内容が含まれています。
  • パッケージがインストールされます。
  • サービスが開始されました。

リソースは相互接続できます。

  • 依存関係があり、リソースが使用される順序に影響します。
    たとえば、「最初にパッケージをインストールし、次に構成ファイルを編集して、サービスを開始します。」
  • 通知があります - リソースが変更された場合、そのリソースにサブスクライブされているリソースに通知が送信されます。
    たとえば、構成ファイルが変更された場合、サービスを自動的に再起動できます。

さらに、Puppet DSL には、関数と変数に加えて、条件文とセレクターもあります。 EPP や ERB など、さまざまなテンプレート メカニズムもサポートされています。

Puppet は Ruby で書かれているため、構成要素や用語の多くは Ruby から取られています。 Ruby を使用すると、Puppet を拡張でき、複雑なロジック、新しいタイプのリソース、関数を追加できます。

Puppet の実行中、サーバー上の特定のノードごとのマニフェストがディレクトリにコンパイルされます。 ディレクトリ 関数、変数、条件文の展開の値を計算した後のリソースとその関係のリストです。

構文とコードスタイル

提供されている例では不十分な場合に、構文を理解するのに役立つ公式ドキュメントのセクションを次に示します。

マニフェストの例を次に示します。

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

インデントと改行はマニフェストの必須の部分ではありませんが、推奨されるものがあります。 スタイルガイド。 まとめ:

  • XNUMX スペースのインデント、タブは使用されません。
  • 中括弧はスペースで区切られますが、コロンはスペースで区切られません。
  • 最後のパラメーターを含め、各パラメーターの後にはカンマが必要です。 各パラメータは別の行にあります。 パラメータがなく、パラメータが XNUMX つの場合は例外です。カンマなしで XNUMX 行に記述できます (つまり、 resource { 'title': } и resource { 'title': param => value }).
  • パラメータ上の矢印は同じレベルにある必要があります。
  • リソース関係の矢印はその前に書かれています。

pappetserver 上のファイルの場所

さらに詳しく説明するために、「ルート ディレクトリ」の概念を紹介します。 ルート ディレクトリは、特定のノードの Puppet 設定が含まれるディレクトリです。

ルートディレクトリは、Puppet のバージョンや使用する環境によって異なります。 環境は、別のディレクトリに保存される独立した構成セットです。 通常は git と組み合わせて使用​​され、その場合環境は git ブランチから作成されます。 したがって、各ノードは XNUMX つの環境または別の環境に配置されます。 これはノード自体、または次の記事で説明する ENC で設定できます。

  • XNUMX 番目のバージョン (「古い Puppet」) では、ベース ディレクトリは次のとおりでした。 /etc/puppet。 環境の使用はオプションです。たとえば、古い Puppet では環境を使用しません。 環境が使用されている場合、通常は次の場所に保存されます。 /etc/puppet/environmentsの場合、ルート ディレクトリが環境ディレクトリになります。 環境を使用しない場合は、ルート ディレクトリがベース ディレクトリになります。
  • XNUMX 番目のバージョン (「新しい Puppet」) からは、環境の使用が必須となり、ベース ディレクトリは次の場所に移動されました。 /etc/puppetlabs/code。 したがって、環境は次の場所に保存されます。 /etc/puppetlabs/code/environments, ルートディレクトリは環境ディレクトリです。

ルートディレクトリにはサブディレクトリが必要です manifests、ノードを説明する XNUMX つ以上のマニフェストが含まれます。 さらに、サブディレクトリがあるはずです modules、モジュールが含まれています。 どのようなモジュールがあるかについては、後ほど説明します。 さらに、古い Puppet にもサブディレクトリがある場合があります。 filesこれには、ノードにコピーするさまざまなファイルが含まれています。 新しい Puppet では、すべてのファイルがモジュールに配置されます。

マニフェスト ファイルの拡張子は次のとおりです。 .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 получает уведомление,
  # соответствующий сервис перезапускается.
}

これが機能するには、Puppet サーバー上におよそ次のファイルの場所が必要です。

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

リソースの種類

サポートされているリソース タイプの完全なリストは、ここにあります。 ドキュメントの中で, ここでは、私の実践ではほとんどの問題を解決するのに十分な XNUMX つの基本的なタイプについて説明します。

file

ファイル、ディレクトリ、シンボリックリンク、それらのコンテンツ、およびアクセス権を管理します。

パラメータ:

  • リソース名 — ファイルへのパス (オプション)
  • path — ファイルへのパス (名前に指定されていない場合)
  • 確保 - ファイルの種類:
    • absent - ファイルを削除する
    • present — 任意のタイプのファイルが存在する必要があります (ファイルがない場合は、通常のファイルが作成されます)
    • file - 通常のファイル
    • directory - ディレクトリ
    • link - シンボリックリンク
  • コンテンツ — ファイルの内容 (通常のファイルにのみ適しており、 source または ターゲット)
  • source — ファイルの内容のコピー元のパスへのリンク ( コンテンツ または ターゲット)。 スキームを使用した URI として指定できます。 puppet: (その後、Puppet サーバーからのファイルが使用されます)、そして次のスキームを使用します http: (この場合に何が起こるか明確であることを願っています)、そして図も含めて file: またはスキーマなしの絶対パスとして (その場合、ノード上のローカル FS のファイルが使用されます)
  • ターゲット — シンボリックリンクが指す場所 (シンボリックリンクと一緒に使用することはできません) コンテンツ または source)
  • 所有者 — ファイルを所有するユーザー
  • グループヘッド — ファイルが属するグループ
  • モード — ファイル権限 (文字列として)
  • 再帰 - 再帰的なディレクトリ処理を有効にする
  • - 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 - 停止
  • enable — サービスを開始する機能を制御します。
    • true — 自動実行が有効になっています (systemctl enable)
    • mask - 変装(systemctl mask)
    • false — 自動実行が無効になっています (systemctl disable)
  • 再起動 - サービスを再起動するコマンド
  • status — サービスのステータスを確認するコマンド
  • 再起動しました — サービスの initscript が再起動をサポートするかどうかを示します。 もし false そしてパラメータが指定されている 再起動 — このパラメータの値が使用されます。 もし false およびパラメータ 再起動 指定されていない - サービスは停止され、再起動が開始されます (ただし、systemd はコマンドを使用します) systemctl restart).
  • ステータス — サービスの initscript がコマンドをサポートするかどうかを示します status。 もし falseの場合、パラメータ値が使用されます status。 デフォルト true.

exec

外部コマンドを実行します。 パラメータを指定しない場合 作成します。, 場合のみ, ない限り、 または リフレッシュのみ、コマンドは Puppet が実行されるたびに実行されます。 通知を処理できる - コマンドを実行します。

パラメータ:

  • リソース名 — 実行するコマンド (オプション)
  • command — 実行されるコマンド (名前に指定されていない場合)
  • path — 実行可能ファイルを探すパス
  • 場合のみ — このパラメータで指定されたコマンドがリターン コード XNUMX で完了した場合、メイン コマンドが実行されます。
  • ない限り、 — このパラメータで指定されたコマンドがゼロ以外のリターンコードで完了した場合、メインコマンドが実行されます。
  • 作成します。 — このパラメータで指定されたファイルが存在しない場合、メインコマンドが実行されます。
  • リフレッシュのみ - もしも trueの場合、コマンドは、この exec が他のリソースから通知を受信した場合にのみ実行されます。
  • cwd — コマンドを実行するディレクトリ
  • user — コマンドを実行するユーザー
  • プロバイダー - コマンドの実行方法:
    • POSIX — 子プロセスは単純に作成されます。必ず指定してください path
    • shell - コマンドはシェルで起動されます /bin/sh、指定できない場合があります path、グロビング、パイプ、その他のシェル機能を使用できます。 通常、特殊文字 (|, ;, &&, || 等)。

cron

cronジョブを制御します。

パラメータ:

  • リソース名 - 単なるある種の識別子
  • 確保 — クラウンジョブの状態:
    • present - 存在しない場合は作成します
    • absent - 存在する場合は削除します
  • command - どのコマンドを実行するか
  • 環境 — コマンドを実行する環境 (環境変数とその値のリスト =)
  • user — どのユーザーからコマンドを実行するか
  • , 時間, 平日, , 月日 — cron を実行するタイミング。 これらの属性のいずれかが指定されていない場合、crontab 内のその値は次のようになります。 *.

パペット 6.0 では cron まるで 箱から取り出した状態 puppetserver にあるため、一般的なサイトにはドキュメントがありません。 でも彼は 箱の中にあります puppet-agent 内にあるため、個別にインストールする必要はありません。 そのドキュメントを参照できます Puppet の第 XNUMX バージョンのドキュメント内または GitHub 上で.

リソース全般について

リソースの一意性の要件

私たちが遭遇する最も一般的な間違いは、 重複した宣言。 このエラーは、同じ種類の同じ名前のリソースがディレクトリ内に XNUMX つ以上存在する場合に発生します。

したがって、もう一度書きます: 同じノードのマニフェストには、同じタイトルの同じタイプのリソースを含めないでください。

場合によっては、同じ名前で異なるパッケージ マネージャーを使用してパッケージをインストールする必要があることがあります。 この場合、パラメータを使用する必要があります nameエラーを回避するには:

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

他のリソースタイプには、重複を避けるために役立つ同様のオプションがあります- name у サービス, command у exec、 等々。

メタパラメータ

各リソース タイプには、その性質に関係なく、いくつかの特別なパラメータがあります。

メタパラメータの完全なリスト Puppet ドキュメント内.

短いリスト:

  • 必要とする — このパラメータは、このリソースがどのリソースに依存しているかを示します。
  • - このパラメータは、どのリソースがこのリソースに依存するかを指定します。
  • 申し込む — このパラメータは、このリソースがどのリソースから通知を受信するかを指定します。
  • 通知する — このパラメータは、どのリソースがこのリソースから通知を受信するかを指定します。

リストされているすべてのメタパラメータは、単一のリソース リンク、または角括弧で囲まれたリンクの配列のいずれかを受け入れます。

リソースへのリンク

リソース リンクは、単にリソースについての言及です。 これらは主に依存関係を示すために使用されます。 存在しないリソースを参照するとコンパイル エラーが発生します。

リンクの構文は次のとおりです。大文字のリソース タイプ (タイプ名に XNUMX つのコロンが含まれている場合は、コロン間の名前の各部分が大文字になります)、次に角括弧で囲まれたリソース名 (名前の大文字と小文字が区別されます)変わらないよ!)。 スペースを入れてはなりません。角括弧はタイプ名の直後に記述されます。

例:

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

依存関係と通知

ドキュメントはこちら。

前述したように、リソース間の単純な依存関係は推移的です。 ちなみに、依存関係を追加するときは注意してください。循環依存関係が作成され、コンパイル エラーが発生する可能性があります。

依存関係とは異なり、通知は推移的ではありません。 通知には次のルールが適用されます。

  • リソースが通知を受信すると、更新されます。 更新アクションはリソースタイプに応じて異なります- exec コマンドを実行します。 サービス サービスを再起動し、 パッケージ パッケージを再インストールします。 リソースに更新アクションが定義されていない場合は、何も起こりません。
  • Puppet の XNUMX 回の実行中に、リソースは XNUMX 回だけ更新されます。 これが可能になるのは、通知には依存関係が含まれており、依存関係グラフにはサイクルが含まれていないためです。
  • Puppet がリソースの状態を変更すると、リソースはサブスクライブしているすべてのリソースに通知を送信します。
  • リソースが更新されると、そのリソースにサブスクライブされているすべてのリソースに通知が送信されます。

未指定のパラメータの処理

原則として、一部のリソースパラメータにデフォルト値がなく、このパラメータがマニフェストで指定されていない場合、Puppet はノード上の対応するリソースのこのプロパティを変更しません。 たとえば、次のタイプのリソースの場合、 file パラメータが指定されていません ownerの場合、Puppet は対応するファイルの所有者を変更しません。

クラス、変数、定義の概要

構成の同じ部分を持つ複数のノードがあるとしますが、相違点もあるとします。そうでない場合は、すべてを XNUMX つのブロックで説明できます。 node {}。 もちろん、構成の同一部分を単純にコピーすることもできますが、一般的にこれは悪い解決策です。構成は大きくなり、構成の一般的な部分を変更すると、多くの場所で同じものを編集する必要があります。 同時に、間違いを犯しやすいものです。一般に、DRY (同じことを繰り返さない) 原則が発明されたのには理由があります。

この問題を解決するには、次のような設計があります。 クラス.

クラス

クラス ポペット コードの名前付きブロックです。 コードを再利用するにはクラスが必要です。

まずクラスを説明する必要があります。 説明自体はどこにもリソースを追加しません。 クラスはマニフェストに記述されます。

# Описание класса начинается с ключевого слова 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}"

人形には 名前空間、それに応じて変数は次のようになります。 可視領域: 同じ名前の変数を異なる名前空間で定義できます。 変数の値を解決する場合、変数は現在の名前空間で検索され、次にそれを囲んでいる名前空間で検索されます。

名前空間の例:

  • global - クラスまたはノードの説明の外にある変数はそこに移動します。
  • ノード説明内のノード名前空間。
  • クラス説明内のクラス名前空間。

変数にアクセスするときのあいまいさを避けるために、変数名に名前空間を指定できます。

# переменная без пространства имён
$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
}

ただし、クラス内のどこかで、これこれの名前の変数が使用されているという「秘密の知識」があるため、この例は不適切です。 この知識を一般化する方がはるかに正確です。クラスはパラメータを持つことができます。

クラスパラメータ はクラス名前空間内の変数であり、クラス ヘッダーで指定され、クラス本体内の通常の変数と同様に使用できます。 パラメータ値はマニフェスト内のクラスを使用するときに指定します。

パラメータはデフォルト値に設定できます。 パラメータにデフォルト値がなく、使用時に値が設定されていない場合、コンパイル エラーが発生します。

上記の例のクラスをパラメータ化し、XNUMX つのパラメータを追加しましょう。XNUMX つ目のパラメータは必須で、設定へのパスです。XNUMX つ目はオプションで、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 と class{'classname':} を含めます。

各クラスは次のタイプのリソースです。 class。 他のタイプのリソースと同様、同じノード上に同じクラスの XNUMX つのインスタンスを存在させることはできません。

を使用して同じノードにクラスを XNUMX 回追加しようとすると、 class { 'classname':} (パラメータが異なるか同一であっても違いはありません)、コンパイル エラーが発生します。 ただし、リソース スタイルでクラスを使用する場合は、そのすべてのパラメーターをマニフェストですぐに明示的に設定できます。

ただし、使用する場合は、 include、その後、クラスは必要に応じて何度でも追加できます。 事実は、 include は、クラスがディレクトリに追加されているかどうかを確認する冪等関数です。 クラスがディレクトリにない場合は追加しますが、すでに存在する場合は何も行いません。 しかし、使用する場合には、 include クラス宣言中にクラス パラメーターを設定することはできません。すべての必須パラメーターは外部データ ソース (Hiera または ENC) に設定する必要があります。 それらについては次の記事で説明します。

定義する

前のブロックで述べたように、同じクラスがノード上に複数回存在することはできません。 ただし、場合によっては、同じコード ブロックを同じノード上で異なるパラメーターで使用できる必要があります。 言い換えれば、独自のリソース タイプが必要になります。

たとえば、PHP モジュールをインストールするには、Avito で次の操作を行います。

  1. このモジュールを含むパッケージをインストールします。
  2. このモジュールの構成ファイルを作成しましょう。
  3. php-fpm の設定へのシンボリックリンクを作成します。
  4. php cliの設定へのシンボリックリンクを作成します。

そのような場合には、次のようなデザインが考えられます。 定義する (定義、定義されたタイプ、定義されたリソース タイプ)。 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' }
}

重複宣言エラーを検出する最も簡単な方法は、定義を使用することです。 これは、定義に定数名のリソースがあり、ノード上にこの定義のインスタンスが XNUMX つ以上ある場合に発生します。

このことから身を守るのは簡単です。定義内のすべてのリソースには、依存する名前が必要です。 $title。 別の方法は、リソースの冪等な追加です。最も単純なケースでは、定義のすべてのインスタンスに共通のリソースを別のクラスに移動し、このクラスを定義 - 関数に含めるだけで十分です。 include 冪等。

リソースを追加するときに冪等性を実現するには、関数を使用するという他の方法もあります。 defined и ensure_resources、それについては次のエピソードでお話します。

クラスと定義の依存関係と通知

クラスと定義により、依存関係と通知の処理に次のルールが追加されます。

  • クラス/定義への依存関係は、クラス/定義のすべてのリソースへの依存関係を追加します。
  • class/define 依存関係は、すべての class/define リソースに依存関係を追加します。
  • class/define 通知は class/define のすべてのリソースを通知します。
  • class/define subscription は、class/define のすべてのリソースをサブスクライブします。

条件文とセレクター

ドキュメントはこちら。

if

ここではすべてが簡単です:

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

ない限り、

until は逆の if です。式が false の場合、コードのブロックが実行されます。

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

場合

ここでも複雑なことは何もありません。 値として正規値 (文字列、数値など)、正規表現、データ型を使用できます。

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

セレクター

セレクターは、次のような言語構造です。 caseただし、コードのブロックを実行する代わりに、値を返します。

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

モジュール

構成が小さい場合は、XNUMX つのマニフェストに簡単に保持できます。 しかし、説明する構成が増えるほど、マニフェスト内のクラスとノードが増え、マニフェストが増大し、作業が不便になります。

さらに、コードの再利用の問題もあります。すべてのコードが XNUMX つのマニフェスト内にある場合、このコードを他のマニフェストと共有するのは困難です。 これら XNUMX つの問題を解決するために、Puppet にはモジュールと呼ばれるエンティティがあります。

モジュール - これらは、別のディレクトリに配置されたクラス、定義、およびその他の Puppet エンティティのセットです。 言い換えれば、モジュールは Puppet ロジックの独立した部分です。 たとえば、nginx を操作するためのモジュールがあり、nginx を操作するために必要なものだけが含まれている場合や、PHP を操作するためのモジュールがある場合などがあります。

モジュールはバージョン管理されており、モジュール間の依存関係もサポートされています。 モジュールのオープンリポジトリがあります - パペットフォージ.

Puppet サーバーでは、モジュールはルート ディレクトリの modules サブディレクトリにあります。 各モジュール内には、マニフェスト、ファイル、テンプレート、ライブラリなどの標準ディレクトリ スキームがあります。

モジュール内のファイル構造

モジュールのルートには、わかりやすい名前の次のディレクトリが含まれる場合があります。

  • 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.

テンプレート

テンプレートが何であるかはあなた自身がよく知っていると思いますが、ここでは詳しく説明しません。 でも念のため残しておきます ウィキペディアへのリンク.

テンプレートの使い方:機能を使ってテンプレートの意味を拡張 template、テンプレートへのパスが渡されます。のようなリソースについては、 file パラメータと一緒に使用されます content。 たとえば、次のようになります。

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

ビューパス <modulename>/<filename> ファイルを暗黙的に示します <rootdir>/modules/<modulename>/templates/<filename>.

また、機能があります inline_template — ファイル名ではなく、テンプレート テキストを入力として受け取ります。

テンプレート内では、現在のスコープ内のすべての Puppet 変数を使用できます。

Puppet は、ERB および EPP 形式のテンプレートをサポートしています。

ERB について簡単に説明します

制御構造:

  • <%= ВЫРАЖЕНИЕ %> — 式の値を挿入します
  • <% ВЫРАЖЕНИЕ %> — 式の値を計算します (式を挿入せずに)。 通常、条件文 (if) とループ (それぞれ) がここに置かれます。
  • <%# КОММЕНТАРИЙ %>

ERB の式は Ruby で書かれています (ERB は実際には Embedded Ruby です)。

マニフェストから変数にアクセスするには、以下を追加する必要があります @ 変数名に。 コントロール構造の後に表示される改行を削除するには、終了タグを使用する必要があります。 -%>.

テンプレートの使用例

ZooKeeper を制御するモジュールを作成しているとします。 構成の作成を担当するクラスは次のようになります。

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 リリースに応じて、パッケージの XNUMX つまたは別のバージョンをインストールする必要があります。 これらすべてを手動で監視し、ノードが変更された場合はマニフェストを書き換えることができます。 しかし、これは本格的なアプローチではなく、自動化の方がはるかに優れています。

ノードに関する情報を取得するために、Puppet にはファクトと呼ばれるメカニズムがあります。 事実 - これはノードに関する情報であり、グローバル名前空間の通常の変数の形式でマニフェストで入手できます。 たとえば、ホスト名、オペレーティング システムのバージョン、プロセッサ アーキテクチャ、ユーザーのリスト、ネットワーク インターフェイスとそのアドレスのリストなどです。 ファクトはマニフェストおよびテンプレートで通常の変数として使用できます。

ファクトを扱う例:

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

正式に言えば、ファクトには名前 (文字列) と値 (文字列、配列、辞書など、さまざまなタイプが使用可能) があります。 食べる 組み込みファクトのセット。 自分で書くこともできます。 ファクトコレクターについて説明します Rubyの関数のようなものまたはどのように 実行可能ファイル。 事実は次の形式で提示することもできます。 データを含むテキスト ファイル ノード上で。

動作中、Puppet エージェントはまず、利用可能なすべてのファクト コレクターを pappetserver からノードにコピーし、その後、ファクト コレクターを起動して、収集したファクトをサーバーに送信します。 この後、サーバーはカタログのコンパイルを開始します。

実行可能ファイルの形式のファクト

このようなファクトはディレクトリ内のモジュールに配置されます。 facts.d。 もちろん、ファイルは実行可能である必要があります。 実行時には、情報を YAML または key=value 形式で標準出力に出力する必要があります。

この事実は、モジュールがデプロイされる Poppet サーバーによって制御されるすべてのノードに適用されることを忘れないでください。 したがって、スクリプトでは、ファクトが動作するために必要なすべてのプログラムとファイルがシステムにあることを注意して確認してください。

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

Rubyの事実

このようなファクトはディレクトリ内のモジュールに配置されます。 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 新しいパペットで。

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

事実を知る

事実にアプローチするには XNUMX つの方法があります。

  • 辞書を通して $facts: $facts['fqdn'];
  • ファクト名を変数名として使用します。 $fqdn.

辞書を使うのが一番いいよ $facts、またはさらに良いのは、グローバル名前空間 ($::facts).

ここにドキュメントの関連セクションがあります。

組み込み変数

事実のほかに、次もあります。 いくつかの変数、グローバル名前空間で使用できます。

  • 信頼できる事実 — クライアントの証明書から取得される変数 (証明書は通常、Poppet サーバー上で発行されるため、エージェントはその証明書を単に取得して変更することはできないため、変数は「信頼されます」): 証明書の名前、ホストとドメイン、証明書の拡張子。
  • サーバーの事実 - サーバーに関する情報に関連する変数 - バージョン、名前、サーバーの IP アドレス、環境。
  • エージェントの事実 — ファクターではなく、puppet-agent によって直接追加された変数 — 証明書名、エージェントのバージョン、puppet のバージョン。
  • マスター変数 - パペットマスター変数 (原文どおり!)。 とほぼ同じです サーバーの事実に加えて、構成パラメータ値も利用できます。
  • コンパイラ変数 — 各スコープで異なるコンパイラ変数: 現在のモジュールの名前と、現在のオブジェクトがアクセスされたモジュールの名前。 これらは、たとえば、プライベート クラスが他のモジュールから直接使用されていないことを確認するために使用できます。

追加 1: これらすべてを実行およびデバッグするにはどうすればよいですか?

この記事には、パペット コードの例が多数含まれていましたが、このコードを実行する方法についてはまったく説明されていませんでした。 まあ、私は自分自身を修正しています。

Puppet を実行するにはエージェントだけで十分ですが、ほとんどの場合、サーバーも必要になります。

エージェント

少なくともバージョン XNUMX 以降では、puppet-agent パッケージは 公式 Puppetlabs リポジトリ すべての依存関係 (ruby と対応する gem) が含まれているため、インストールに困難はありません (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

もちろん、サーバーをセットアップし、デーモン モードでノード上でエージェントを実行する方が良いでしょう。そうすれば、サーバーからダウンロードされた構成が XNUMX 分に XNUMX 回適用されます。

作業のプッシュ モデルを模倣できます。興味のあるノードに移動して開始します。 sudo puppet agent -t。 鍵 -t (--test) には、実際には個別に有効にできるいくつかのオプションが含まれています。 これらのオプションには次のものが含まれます。

  • デーモン モードで実行しないでください (デフォルトでは、エージェントはデーモン モードで起動します)。
  • カタログの適用後にシャットダウンします (デフォルトでは、エージェントは動作を継続し、XNUMX 分ごとに構成を適用します)。
  • 詳細な作業記録を書きます。
  • ファイル内の変更を表示します。

エージェントには変更なしの動作モードがあります。これは、正しい設定を記述したかどうか確信が持てず、動作中にエージェントがどのように変更するかを正確に確認したい場合に使用できます。 このモードはパラメータによって有効になります --noop コマンドラインで: sudo puppet agent -t --noop.

さらに、作業のデバッグ ログを有効にすることができます。このログには、puppet が実行するすべてのアクション (現在処理中のリソース、このリソースのパラメータ、起動するプログラムなど) が書き込まれます。 もちろんこれはパラメータです --debug.

Сервер

この記事では、pappetserver の完全なセットアップとそれにコードをデプロイすることについては考慮しません。少数のサーバーで動作するために追加の構成を必要とせず、すぐに使える完全に機能するバージョンのサーバーがあることだけを述べます。ノード (たとえば、最大 XNUMX)。 ノードの数が増えると調整が必要になります。デフォルトでは、puppetserver は XNUMX つ以下のワーカーを起動します。パフォーマンスを向上させるには、ワーカーの数を増やす必要があり、メモリ制限を増やすことを忘れないでください。そうしないと、サーバーはほとんどの場合ガベージ コレクションを実行します。

コードのデプロイ - 迅速かつ簡単に必要な場合は、(r10k)[を参照してください。https://github.com/puppetlabs/r10k]、小規模なインストールの場合は、これで十分です。

付録 2: コーディングガイドライン

  1. すべてのロジックをクラスと定義に配置します。
  2. クラスと定義は、ノードを記述するマニフェストではなく、モジュールに保持します。
  3. 事実を利用しましょう。
  4. ホスト名に基づいて if を作成しないでください。
  5. クラスや定義のパラメータを自由に追加してください。これは、クラス/定義の本体に暗黙的なロジックを隠すよりも優れています。

これを推奨する理由については、次の記事で説明します。

まとめ

紹介はこれで終わりにしましょう。 次回の記事では、Hiera、ENC、PuppetDB について説明します。

登録ユーザーのみがアンケートに参加できます。 ログインお願いします。

実際には、さらに多くの資料があります。次のトピックに関する記事を書き、あなたが読みたいものに投票できます。

  • 視聴者の38%が高度な Puppet 構造 - 次のレベルのたわごと: ループ、マッピング、その他のラムダ式、リソース コレクター、エクスポートされたリソース、および Puppet、タグ、プロバイダー、抽象データ型を介したホスト間通信。13
  • 視聴者の38%が「私は母親の管理者です」、または Avito でどのようにしてさまざまなバージョンの複数のポペット サーバーと友達になったか、そして原則としてポペット サーバーの管理に関する部分です。7
  • 視聴者の38%がPuppet コードの書き方: インストルメンテーション、ドキュメント、テスト、CI/CD.18

22 人のユーザーが投票しました。 9名のユーザーが棄権した。

出所: habr.com