見出し画像

AWX/TowerのJob TemplateをPlaybookで追加する方法

こんにちは。株式会社SHIFT、自動化エンジニアの水谷です。

最近AWX(AnsibleのPlaybookやInventoryなどを登録して、Playbookの実行を行ったり、結果を管理するためのWebツール)上に作った数十個のJob Template(実行するPlaybookの指定、および実行時の条件等を指定するオブジェクト)を、別のサーバー上で動いているAWX上に移動する必要がありました。もちろん、1つずつ新しいサーバー上にAWXのUIから手作業でJob Templateを作っていくのは手間も時間もかかるので、(一応自動化エンジニアと名乗っている手前もありw)この作業を自動化したいと思いました。

いろいろ考えた結果、Job Templateをエクスポート⇒インポートするのではなく、PlaybookでJob Templateを作る方法をとったのですが、今回はそのあたりの話をまとめておきたいと思います。


tower-cliとawxkit

まずは調査、ということでネットを検索して調べたところ、"tower-cli"というコマンドラインツールがあって、これでAWX上に登録してあるリソースをエクスポートしたり、インポートできるという情報を見つけました。

やっぱりちゃんとツールが用意されているんだな、と思いながら、さっそくtower-cliのGithubを見てみると、なんと下のように「THIS PROJECT IS NO LONGER MAINTAINED(このプロジェクトは今後メンテナンスされない、要するに開発終了)」の文字が。

画像1

https://github.com/ansible/tower-cli

これはつまり、今後AWXに大きな変更が入ったりした際には、tower-cliが動作しなくなる可能性があることも意味しているので、ちょっと躊躇してしまいます(今回1回だけ使うなら大丈夫かもしれませんが・・・)。

ということで、再度調べてみると、今度は"awxkit"というツールがあって、これがtower-cliを置き換える存在との情報を発見。

公式ドキュメント(https://docs.ansible.com/ansible-tower/latest/html/towercli/examples.html)の下のページなどを見てみると、"awx export --jobtemplate > jt.json"と、とても簡単なコマンドで、Job Templateを(Surveyも含めて)全部JSON形式で出力してくれるようです。

そして、そのファイルを新しいサーバー上で"awx import < jt.json"コマンドでインポートしてやればよいようです。簡単ですね。

それでもやっぱりPlaybook化したい

awxkitを使えばJob Templateのサーバー間移動ができることはわかりました。しかし、ふとメンテナンス性について考えると、これでいいのかな? と、ちょっと疑問に思いました。

例えば、今後複数のマシンにJob Templateをインポートして展開していった状態で、Job Templateの一部を変更する必要が生じた場合を考えてみます。すべてのマシンのJob Templateを同じように更新したいのですが、この場合取れる方法は2つあって、1つは一度一部あるいは全部のJob Templateを削除して、修正後の(Job Templateが入った)JSONファイルをインポートする。2つ目の方法は、"awx jobtemplates modify"コマンドで各サーバーにおいてJob Templateを(パッチを当てる感じで)修正する。他にもあるのかもしれませんが、私が思いついたこの2つの方法では、どちらにしても1台1台のサーバーに対して手作業でアップデート作業を行うことになり、結局中途半端な自動化になってしまいます。

そこで、何かうまい方法はないかと考えた結果、Job Templateを作成するPlaybookを作って、それを各マシンで自動実行することで、自動的にJob Templateを追加したり修正したりできるようにしてしまおう、という考えに至りました。

tower_job_templateモジュール

そもそもJob TemplateをPlaybookで作成なんてできるのか?と思われるかと思いますが、実はそのためのモジュール"tower_job_template"がAnsibleには用意されています。

画像2

https://docs.ansible.com/ansible/latest/collections/awx/awx/tower_job_template_module.html

Playbookのタスクをこのモジュールで作成して、そこにJob Template名、実行するPlaybook名など各項目を変数として記述していけば、1つのJob Templateを作成するタスクができあがります。下はその例で、ChromeをインストールするPlaybookを実行するためのJob Templateを作るタスクを含むPlaybook(ややこしいw)となります。

---
- name: Add a Job Template
  hosts: localhost
  tasks:
    - name: Add Job Template for Install Chrome Playbook
      tower_job_template:
        name: Install Chrome
        tower_username: "{{ lookup('env', 'TOWER_USERNAME') }}"
        tower_password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
        tower_host: "{{ lookup('env', 'TOWER_HOST') }}"
        job_type: run
        inventory: Test Inventory
        project: TestPlaybookProject
        playbook: windows/windows-install-chrome.yml
        credential: Machine Account
        state: present

このPlaybookはAnsibleが動作しているマシンで実行するので、"hosts:"は、"localhost"です。また、Job Templateを作成するAWX(あるいはAnsible Tower)の情報は、環境変数から取ってくるようにしています。事前に"TOWER_HOST"、"TOWER_USERNAME"、および"TOWER_PASSWORD"の環境変数を下の例ように設定しておきます。

export TOWER_HOST=http://1.1.1.1/  # 実際のAWXのIPアドレスに変更してください
export TOWER_USERNAME=admin
export TOWER_PASSWORD=password

AWXやTowerを使われている方は、「Survey機能はどうするの?」と、疑問に思われるかと思います。もちろん、Survey(Playbbok実行時に表示される入力フォーム)も合わせて登録できるようになっています。

その方法は、"survey_enabled: true"という行を追加し、さらにSurveyの内容を記述したJSONファイルを用意して、下のように"survey_spec"変数にそのファイル内容を取り込みます。

     survey_enabled: true
     survey_spec: "{{ lookup('file', 'survey-install-chrome.json') }}"

では、そのJSONファイルの内容はというと、例えば下のようなものです。

{
    "name": "",
    "description": "",
    "spec": [
        {
            "question_name": "Chocolateyの使用",
            "question_description": "Chocolateyを使用してインストール",
            "required": true,
            "type": "multiplechoice",
            "variable": "use_chocolatey",
            "min": 0,
            "max": 255,
            "default": "no",
            "choices": "yes\nno",
            "new_question": true
        }
    ]
}

このSurveyは、Chromeのインストールにパッケージ管理ソフトのChocolateyを使うかどうかをyes/noで選択するものです。選択結果は文字列として"use_chocolatey"変数に入ることになります。

※ "type"は、"text"(テキスト形式)、"multiplechoice"(複数の選択しから1つを選ぶ。選択肢は"choices"に"\n"区切りで書く)、"multiselect"(複数の選択肢から複数を選ぶ)などが指定できます

ちなみに、なぜAnsibleはYAML形式で書くのにここだけJSONファイルなのかは、わかりません。一部のドキュメントではYAMLでも書けるような記述もありましたが、少し試してみたところ、YAMLでは動きませんでした。

いずれにせよ、このPlaybookを実行すればSurvey付きのJob TemplateがAWX(あるいはTower)に追加されます。あとは、どんどんタスクを増やしていけばOK、ということになります。

Playbook作成も面倒なので自動化

「どんどんタスクを増やしていけばOK」、と書いたものの、何十ものPlaybookについてタスクを手で書いていくのはこれまた手間がかかるし、ミスも起こしそうです。

ということで、これも自動化しましょう。

AnsibleはLinux系OS上でPythonを使って動いているので、なんとなくPythonでPlaybookを生成するスクリプトを書くのが自然かな、と思ったりもしましたが、私はそれほどPythonが流暢には使えないので、慣れているPowershellで下のようなコードを書いてみました(ベタだし、あまりきれいではないですが・・・)。これで、フォルダ内のすべてのPlaybookから必要な情報を拾ってきてJob Template作成タスクを作っていきます。Surveyについては"survey"フォルダにPlaybookと同じファイル名で拡張子が".json"のファイルがあれば"Surveyあり"と判断し"survey_enabled:"の行と"survey_spec:"の行を追加するようにしています。

$ScriptDir = $PSScriptRoot
$PackageRoot = Split-Path $ScriptDir -Parent
$dir = "playbook_directory"
$playbook = Join-Path $PackageRoot "GenerateJobTemplates.yml"
$temp = "---`r`n"
$temp += "- name: Add Job Templates for " + $dir + "`r`n"
$temp += "  hosts: localhost`r`n`r`n"
$temp += "  tasks:`r`n"
gci (Join-Path $PackageRoot $dir) | ? name -like "*.yml" | foreach {
    $pb = gc $_.FullName
    # find name
    $name = $pb -match '^\s{2}name'
    if ($name.length -eq 0) { $name = $pb -match '^-\s{1}name'; $name = " " + $name.substring(1) }
    $temp += "  -" + $name.Substring(1) + "`r`n"
    $temp += "    tower_job_template:`r`n"
    $temp += "    " + $name  + "`r`n"
    $temp += "      tower_username: `"{{ lookup('env', 'TOWER_USERNAME') }}`"`r`n"
    $temp += "      tower_password: `"{{ lookup('env', 'TOWER_PASSWORD') }}`"`r`n"
    $temp += "      tower_host: `"{{ lookup('env', 'TOWER_HOST') }}`"`r`n"
    $temp += "      job_type: run`r`n"
    $temp += "      inventory: Playbook Package Inventory`r`n"
    $temp += "      project: PlaybookPackage`r`n"
    $temp += "      playbook: $dir/$($_.Name)`r`n"
    $temp += "      credential: $dir-target`r`n"
    $temp += "      limit: $dir`r`n"
    $temp += "      state: present`r`n"
    # survey
    $surveyfile = Join-Path (Join-Path (Join-Path $PackageRoot $dir) "survey") ($_.name).Replace(".yml", ".json")
    if (Test-Path $surveyfile) {
        $temp += "      survey_enabled: true`r`n"
        $temp += "      survey_spec: `"{{ lookup('file', '{{ playbook_dir | dirname }}/$dir/survey/$($_.Name.Replace(".yml", ".json"))') }}`"`r`n"
    }
    $temp += "`r`n"
}
while ($temp.LastIndexOf("`r`n") -eq $temp.length - 2) { $temp = $temp.Substring(0, $temp.length -2) }
[System.IO.File]::WriteAllLines($playbook, $temp)

SurveyのJSONファイルを用意するのがちょっと手間ですが、これを実行すればディレクトリ内にあるすべてのPlaybookに対するJob Templateを自動生成するPlaybookができます。

あとは実行するだけ。

実行にはtower-cliが必要!?

しかし、実際にPlaybookを実行して見ると、下のようなエラーが出ました。

画像3

エラーの内容は、"tower-cli"がない(インストールされていない)というものです。なんと、もうメンテナンスしないと宣言しているtower-cliがこのモジュール(tower_job_template)では現役で使われているようです。

いずれはモジュールの中で動くtower-cliはawxkitに置き換わるのかなと想像できますが、今はtower-cliをインストールして動かすしかなさそうです。

tower-cliはpip(pip3)でインストールできます。

$ pip3 install ansible-tower-cli

これで、Playbookが実行できます。

Job Templateを追加するPlaybookのJob Template

さて、最後の仕上げ(?)です。このPlaybookをGitにPushしておき、Job Templateを作ってAWX上から定期実行するようにします。こうすれば、例えば1週間に1度最新のJob Template作成PlaybookがGitから取り込まれて実行され、その結果Job Templateが追加されたり、既存のJob Templateも自動的に更新されるようになります。

※tower_job_templateモジュールには冪等性がありますので、同じJob Templateが2つできたりはしません。

ここで1つ注意が必要な点は、tower-cliをAWXでAnsibleが動作するコンテナ内にインストールしておく必要があることです。AWX上でPlaybookを実行する場合、コンテナ内のAnsibleが使われるため、tower-cliそのコンテナ内に存在する必要があるのです。

まずはAWXマシンにSSH接続し、"docker ps"を実行します。ここで一番上に表示されるコンテナがそれです。

$ sudo docker ps
CONTAINER ID   IMAGE                COMMAND                  CREATED       STATUS       PORTS                  NAMES
f65af3afdeab   ansible/awx:15.0.1   "/usr/bin/tini -- /u…"   10 days ago   Up 2 hours   8052/tcp               awx_task
f8d806b411af   ansible/awx:15.0.1   "/usr/bin/tini -- /b…"   10 days ago   Up 2 hours   0.0.0.0:80->8052/tcp   awx_web
ec04fc4ad044   redis                "docker-entrypoint.s…"   10 days ago   Up 2 hours   6379/tcp               awx_redis
4f0fc638c4ed   postgres:10          "docker-entrypoint.s…"   10 days ago   Up 2 hours   5432/tcp               awx_postgres

上の例であれば、IDが"f65af3afdeab"なので、以下のコマンドでコンテナに入ります。

$ sudo docker exec -t -i f65af3afdeab /bin/bash

そして、pip3で下のようにtower-cliをインストールします。

$ pip3 install ansible-tower-cli

あとは、環境変数の"TOWER_HOST"、"TOWER_USERNAME"、および"TOWER_PASSWORD"を設定すればOKとなります。なお、tower-cliの動作を確認するには、"tower-cli user list"を実行して、AWX(あるいはTower)に登録したユーザーの情報が表示されるかどうかを見ればよいかと思います。

これで、Playbookを修正したり、追加したりした場合、上のPowershellスクリプトを実行してGitにPushすれば、すべてのマシンにおいて定期実行されるJobでJob Templateが追加されたり修正されることになり、メンテナンスがとても楽になります(Powershellスクリプトの実行をCI化してしまえばなお良し)。

――――――――――――――――――――――――――――――――――

執筆者プロフィール:水谷 裕一
大手外資系IT企業で15年間テストエンジニアとして、多数のプロジェクトでテストの自動化作業を経験。その後画像処理系ベンチャーを経てSHIFTに入社。
SHIFTでは、テストの自動化案件を2件こなした後、株式会社リアルグローブ・オートメーティッド(RGA)にPMとして出向中。RGAでは主にAnsibleに関する案件をプレーイングマネジャーとして担当している。

公式noteお問合せ画像

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/