【Ansible】block-whenという書き方について

f:id:hira98:20190817155514p:plain

【結論】

  • Ansibleにif-elseはないけど、block-whenはあった。

  • block-whenという書き方が最適解かどうかはご自身で判断ください😑

  • Don't repeat yourself (DRY)信者がAnsibleと付き合うには妥協が必要。

【目次】

はじめに

現在業務でAnsibleを使って、ファイアウォール(FW)のプロビジョニング作業を自動化するための開発を行っています。

システム構成の概略は下図になります。

(FWのプロビジョニングに関係する部分だけを記載しています)

f:id:hira98:20190817155600p:plain

システムの概要をざっくりいうと、A,B,Cユーザーが各々で管理していたサーバーを一箇所で管理するためのシステムです。

新たにxxユーザーを追加する場合は次の手順で行えます。

  1. xxユーザー専用のサーバーを用意する。
  2. ユーザー情報を構成管理DBに登録する。
  3. Ansibleのプレイブックを実行すると、xxサーバーの設定を行い、FW_1とFW_2の穴あけ作業を自動で行ってくれる。

と言う一連の流れの内、私はFW_1とFW_2の穴あけ作業の自動化部分を担当しています。

FW(ファイアウォール)のプロビジョニング内容

1台のFWへのプロビジョニングは次の手順で行います。

  1. 事前確認(正常な状態か?あける予定の穴はまだあいていないか?...etc)
  2. 設定処理
  3. 事後確認(穴はきちんとあいたか?設定中に状態異常になっていないか?...etc)

今回のシステム構成では2台のFWに対してプロビジョニングを行います。

その場合の処理の流れを表したのが下図になります。

f:id:hira98:20190817155619p:plain

赤四角はFWに対して操作を行なっている部分です。FWによって必要なデータが変わってくるため、個別に渡してあげる必要があります。

また、FW_2では一部行う必要のない処理もあるため、赤四角の破線で表しています。

実装方針

FW_1とFW_2の差は渡すデータだけです。

よって、データは事前に定義しておき、事前確認、設定、事後確認は共通化する方針で実装しました。

全ソースを乗せると長くなる上に、事前確認、設定、事後確認はほぼ同じであるため、事前確認部分のソースだけを載せます。

【ファイル構成】

$ tree
├── fw_main.yml
├── tasks
    ├── fw_provisioning.yml
    └── set_config.yml

【ソース①(./fw_main.yml)】

---
- hosts: localhost  # 自分自身に接続する
  tasks:

  - name: 設定情報の定義
    include_task: set_config.yml

  - include_task: fw_ provisioning.yml
    vars:
      in_config: "{{ item }}"
    with_items:
      - "{{ fw1_config }}"
      - "{{ fw2_config }}"

【ソース②(./tasks/set_config.yml)】

---
# 構成管理DBからデータ取得
# (省略)

# FW_1とFW_2の設定情報定義
- name: 接続先情報の定義
  set_fact:
    fw1_setting:
      pre_check_1: "確認内容1"
      pre_check_2: "確認内容2"
      pre_check_3: "確認内容3"
      # 設定処理と事後チェック用の定義は省略
    fw2_setting:
      pre_check_1: "確認内容1"
      pre_check_2: "確認内容2"
      pre_check_3: false
      # 設定処理と事後チェック用の定義は省略

【ソース③(./tasks/fw_provisioning.yml)】

---
- name: 変数設定
  set_fact:
    pre_check_1: "{{ in_config.pre_check_1 }}"
    pre_check_2: "{{ in_config.pre_check_2 }}"
    pre_check_3: "{{ in_config.pre_check_3 }}"

# -------------------------------
# 事前チェック
- block:
  - name: 事前チェック1
    include_task: fw_pre_check1.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_1 }}"
  when: ( pre_check_1 != false)

- block:
  - name: 事前チェック2
    include_task: fw_pre_check2.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_2 }}"
  when: ( pre_check_2 != false)

- block:
  - name: 事前チェック3
    include_task: fw_pre_check3.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_3 }}"
  when: ( pre_check_3 != false)

# -------------------------------
# 設定処理と事後チェックは省略

自己満足ポイントは、FW_1とFW_2のデータは共通で定義し、FW_2で必要ない定義はfalseを設定しておき、【ソース③(./tasks/fw_provisioning.yml)】で.ymlを呼ぶかどうかをblock-whenで判定するようにしている点です。

【ソース③(./tasks/fw_provisioning.yml)】で呼んでいる事前チェック1〜3の.ymlもループを回せば記述量を減らせそうですが、そこまでやると可読性が下がる上に、Ansibleにそこまで求めると沼にはまってしまうので目を瞑っています。

補足

ちなみに、block-whenは私が勝手に言っているだけで、本来のblockは例外処理を実装するために用意されています。

  • Ansible→block-rescue-always
  • Javatry-chatch-finally
  • Pythontry-except-finally
  • Rubybegin-rescue-ensure-end

さいごに

1月程Ansibleと関わって身にしみたことは、「Don't repeat yourself (DRY)信者がAnsibleと付き合うには、

過度な期待や理想を追い求めず、信念を曲げて妥協に妥協を重ねる必要がある」ことを理解しました。