はじめに
こんにちは!
しばらく期間が開いてしまいました。
毎年6月はITイベントが多くて刺激になりますね!
今年も無事AWSサミット参加できてほっとしています。
本題に戻して、前回のおさらいから!
前回までの記事では自動起票編ということで、ZabbixがNW機器の障害を検知したときにServiceNowへ自動でインシデントが起票され、復旧時に自動クローズされるところまで確認しましたね。
前回の記事はこちら
【自動起票編】Zabbix × ServiceNow × AWX × Bedrockで障害初動対応を自動化してみた
今回はその続きとしてAWX連携編を書いていこうと思います。
インシデントが起票されるところまではできましたが
このままだと「障害が起きたことは分かるけどログ取得は手動でNW機器にログインして取得する」という状態です。
そこで今回は、インシデント起票と同時にAWXのJobを自動起動して、NW機器のログを自動収集するところまで実装していきます。
今回のゴール
Zabbixが障害を検知してServiceNowにインシデントが起票されるタイミングで、AWXのJobが自動起動し、Cisco機器から複数のshowコマンドの結果を取得してS3に保存されることをゴールとします。
[Zabbix] 障害検知
↓
[Lambda] ServiceNowにインシデント起票
↓
[Lambda] AWX APIを呼び出してJob起動
↓
[AWX] Ansible Playbookを実行
↓
[Cisco機器] showコマンドを複数実行
↓
[S3] ログを保存
今回の流れ
| # | 内容 |
|---|---|
| 0 | 事前準備(S3バケット作成・GitHubリポジトリ作成) |
| 1 | Playbookの作成 |
| 2 | GitHubリポジトリへのプッシュ |
| 3 | AWXのCredentials設定(踏み台・Cisco・GitHub) |
| 4 | AWXのInventory設定 |
| 5 | AWXのProject設定(GitHub連携) |
| 6 | AWXのJob Template作成 |
| 7 | AWX APIトークンの発行 |
| 8 | 単体確認(AWX UIから手動実行) |
なぜAWXを使うのか
Playbookを直接叩く方法でも動きますが、あえてAWXを挟むことで以下が実現できます。
| 観点 | Playbook直叩き | AWX経由 |
|---|---|---|
| 実行方法 | サーバにログインしてコマンド実行 | REST APIで呼び出すだけ |
| 認証情報の管理 | スクリプト内やサーバ内に平文で持ちがち | Credentials機能で一元管理・暗号化保存 |
| 他システムとの連携 | 難しい(実行環境が必要) | LambdaなどからHTTP1本で起動可能 |
| 実行履歴 | 自分でログを残す必要あり | AWXが自動で記録・GUIで確認可能 |
今回のように「Lambdaからログ収集を自動起動したい」というケースでは、AWXをAnsibleの実行をAPI化してくれるレイヤーとして使うのがポイントです。
Lambda側はAWXの中身を意識せず、Job Templateの番号を指定してAPIを叩くだけで済みます。
事前準備:バケットとリポジトリを用意する
S3バケットの作成(AWSマネジメントコンソール)
まずはログ保管先のバケットから作成しましょう。
| 項目 | 値 | 理由 |
|---|---|---|
| バケット名 | nw-verify-logs | 一意な名前である必要があります(既に他アカウントで使われている場合は末尾に自分の識別子を付けます) |
| AWSリージョン | アジアパシフィック(東京) ap-northeast-1 | 他のリソース(EC2/Lambda)と同じリージョンに揃えることで、リージョン間の通信を避けレイテンシとコストを抑えます |
| オブジェクト所有者 | ACL無効(推奨) | バケットポリシーとIAMだけでアクセス制御する、現在のAWS推奨構成です |
| パブリックアクセスをすべてブロック | チェックを入れたまま(デフォルト) | 障害ログという社内情報を誰でも見られる状態にしないためです。インターネットに公開する必要は一切ありません |
| バケットのバージョニング | 無効のままでOK | 今回はログの追記保存が目的で、同名ファイルを上書きする想定がないため、バージョン管理までは不要です |
設定したら「バケットを作成」をクリックします。
GitHubリポジトリの作成
GitHub → 右上の「+」→ New repository
| 項目 | 値 | 理由 |
|---|---|---|
| Repository name | nw-log-collector | AWXのProject設定時に、このリポジトリ名をURLとして指定するため |
| Description | 任意 | 省略可 |
| Public / Private | Private推奨 | 社内NW機器の情報(ホスト名など)が含まれるため、外部に公開する必要はありません |
| Initialize this repository with a README | チェックを入れる | 空のリポジトリだと初回のgit cloneが挙動しづらいため、READMEだけ入れておきます |
「Create repository」をクリックします。
確認ポイント:https://github.com/(ユーザー名)/nw-log-collector にアクセスして、README.mdが表示されていればOKです。
ローカル環境にはこのリポジトリをcloneしておきます。
Playbookの作成
設計の整理
まずは何をするPlaybookなのかを整理します。
① Cisco機器にSSH接続(踏み台EC2経由)
↓
② 事前に定義した複数のshowコマンドを実行
↓
③ コマンド名付きで結果を整形
↓
④ S3バケットにアップロード
収集するコマンドは以下のように、障害調査でよく見るものを中心に選定しています。
commands:
- show version
- show interfaces status
- show ip interface brief
- show logging
- show interfaces GigabitEthernet1/0/1
# ...(実際は19個のコマンドを実行)
Playbookの実装
それではPlaybookの作成に取り掛かります。
collect_logs.yml として以下を作成します。
---
- name: Cisco機器ログ取得
hosts: all
gather_facts: no
vars:
ansible_network_os: cisco.ios.ios
ansible_connection: network_cli
ansible_become: false
ansible_ssh_common_args: '-o ProxyJump=ubuntu@172.16.1.166 -o StrictHostKeyChecking=no'
tasks:
- name: show versionを実行する
cisco.ios.ios_command:
commands: show version
register: r01
- name: show interfacesを実行する
cisco.ios.ios_command:
commands: show interfaces
register: r02
- name: show ip interface briefを実行する
cisco.ios.ios_command:
commands: show ip interface brief
register: r03
- name: show running-configを実行する
cisco.ios.ios_command:
commands: show running-config
register: r04
- name: show vlan briefを実行する
cisco.ios.ios_command:
commands: show vlan brief
register: r05
- name: show spanning-treeを実行する
cisco.ios.ios_command:
commands: show spanning-tree
register: r06
- name: show mac address-tableを実行する
cisco.ios.ios_command:
commands: show mac address-table
register: r07
- name: show arpを実行する
cisco.ios.ios_command:
commands: show arp
register: r08
- name: show cdp neighbors detailを実行する
cisco.ios.ios_command:
commands: show cdp neighbors detail
register: r09
- name: show loggingを実行する
cisco.ios.ios_command:
commands: show logging
register: r10
- name: show clockを実行する
cisco.ios.ios_command:
commands: show clock
register: r11
- name: show inventoryを実行する
cisco.ios.ios_command:
commands: show inventory
register: r12
- name: show processes cpu sortedを実行する
cisco.ios.ios_command:
commands: show processes cpu sorted
register: r13
- name: show environment allを実行する
cisco.ios.ios_command:
commands: show environment all
register: r14
- name: show power inlineを実行する
cisco.ios.ios_command:
commands: show power inline
register: r15
- name: ログをS3に保存する
amazon.aws.s3_object:
bucket: nw-verify-logs
object: "logs/{{ inventory_hostname }}/{{ lookup('pipe', 'date +%Y%m%d_%H%M%S') }}.log"
content: |
========================================
# show version
========================================
{{ r01.stdout[0] }}
========================================
# show interfaces
========================================
{{ r02.stdout[0] }}
========================================
# show ip interface brief
========================================
{{ r03.stdout[0] }}
========================================
# show running-config
========================================
{{ r04.stdout[0] }}
========================================
# show vlan brief
========================================
{{ r05.stdout[0] }}
========================================
# show spanning-tree
========================================
{{ r06.stdout[0] }}
========================================
# show mac address-table
========================================
{{ r07.stdout[0] }}
========================================
# show arp
========================================
{{ r08.stdout[0] }}
========================================
# show cdp neighbors detail
========================================
{{ r09.stdout[0] }}
========================================
# show logging
========================================
{{ r10.stdout[0] }}
========================================
# show clock
========================================
{{ r11.stdout[0] }}
========================================
# show inventory
========================================
{{ r12.stdout[0] }}
========================================
# show processes cpu sorted
========================================
{{ r13.stdout[0] }}
========================================
# show environment all
========================================
{{ r14.stdout[0] }}
========================================
# show power inline
========================================
{{ r15.stdout[0] }}
mode: put
region: ap-northeast-1
delegate_to: localhost
コマンドを1個ずつ実行するのではなくios_commandにリストで渡してまとめて実行するようにしています。
また、取得結果を見やすくするために===== コマンド名 =====区切りで整形しているのがポイントです。
これにより、S3に保存されたログを見るだけでどのコマンドの結果かが一目で分かるようになります。
GitHubリポジトリへのプッシュ
GitHubへのプッシュにはパスワード認証ではなくPersonal Access Token(PAT)が必要です。
作成してない場合は以下で発行します。
GitHub → 右上アイコン → Settings → Developer settings → Personal access tokens → Tokens(classic) → Generate new token
| 項目 | 値 |
|---|---|
| Note | awx-access |
| Expiration | 90days |
| scope | repoにチェック |
AWXのCredentials設定
続いてAWX側の設定に入ります。
AWXでは「誰の権限で」「何に対して」実行するかをCredentialsという単位で管理します。
今回作るのは2種類です。「踏み台用」と「GitHub用」で、Cisco機器の認証はここでは作りません(理由は後述のInventory設定の章で説明します)。
| Credential名 | タイプ | 用途 |
|---|---|---|
| nw-verify-bastion-credential | Machine | 踏み台EC2へのSSH接続用 |
| github-credential | Source Control | GitHubからPlaybookを取得する用 |
なぜこの2つを分けるのかというと、認証情報の性質が違うからです。踏み台への接続は「SSH鍵によるサーバーへの入口の認証」、GitHubへの接続は「PATによるソースコード取得の認証」で、どちらも役割がはっきり分かれています。混在させず別々に管理しておくことで、片方を変更したときにもう片方まで巻き込んで壊れるリスクを減らせます。
無知だったのですが、AWXのMachineタイプの認証情報スロットはJob Template1つにつき1個しか設定できないという制約があるみたいです。
今回はこの1枠を踏み台用に使うため、Cisco機器の認証情報をもう1つMachineタイプで作ってここに追加するということができませんでした。
NetworkタイプのCredentialを追加する方法もありますが、その場合Playbook側に変換処理を追加する必要が出て複雑になります。今回はあくまで検証なので認証はシンプルにInventory側へ直書きする方針にします。
github-credentialの作成
まずはgithub-credentialの作成をします。
AWX UI → 左メニュー「Credentials」→「Add」
| 項目 | 値 |
|---|---|
| Name | github-credential |
| Organization | Default |
| Credential Type | Source Control |
| Username | (GitHubユーザー名) |
| Password | 先ほど作成したPAT |
「Save」をクリックします。
nw-verify-bastion-credentialの作成
AWX UI → 左メニュー「Credentials」→「Add」
| 項目 | 値 |
|---|---|
| Name | nw-verify-bastion-credential |
| Organization | Default |
| Credential Type | Machine |
| Username | ubuntu |
| SSH Private Key | 踏み台EC2の秘密鍵の中身を貼り付け |
「Save」をクリックします。
AWXのInventory設定
InventoryはAWXが「どのホストに対して実行するか」を管理する台帳です。
AWX UI → 左メニュー「Inventories」→「Add」→「Add Inventory」
| 項目 | 値 |
|---|---|
| Name | nw-verify-inventory |
| Organization | Default |
作成後、「Hosts」タブからホストを追加します。
| 項目 | 値 |
|---|---|
| ホスト名 | devnetsandboxiosxec9k.cisco.com |
ホスト変数(Variables)の設定
ホスト詳細画面の「Variables」に、接続方式を指定します。
ansible_host: devnetsandboxiosxec9k.cisco.com
ansible_network_os: cisco.ios.ios
ansible_connection: network_cli
ansible_become: false
ansible_user: DevNet ユーザー名
ansible_password: "DevNet パスワード"
ansible_ssh_common_args: '-o ProxyJump=ubuntu@172.16.1.166 -o StrictHostKeyChecking=no'
ansible_ssh_common_argsのProxyJumpがポイントです。DevNet SandboxのCisco機器はSNMP/ICMPがブロックされている上、直接インターネットからSSH接続できない構成のため、踏み台EC2(172.16.1.166)を経由してSSH接続する設定をここで行っています。


なぜInventory側の変数ではなくJob Template側でも同じ設定をするのか(後述)と疑問に思うかもしれませんが、Inventory側は「このホストに接続する際は常にこの経路を通る」という恒久設定、Job Template側は「このJob実行時に限り上書きする」という一時設定という役割分担になっています。基本的にはInventory側の設定だけで動きます。
AWXのProject設定(GitHub連携)
ProjectはAWXが「どこからPlaybookを取得するか」を定義する設定です。ここでようやくGitHubリポジトリとAWXがつながります。
AWX UI → 左メニュー「Projects」→「Add」
| 項目 | 値 |
|---|---|
| Name | nw-log-collector |
| Organization | Default |
| Source Control Type | Git |
| Source Control URL | https://github.com/(ユーザー名)/nw-log-collector.git |
| Source Control Credential | github-credential |
「Save」をクリックすると、保存と同時にAWXが自動でGitHubからPlaybookを取得しにいきます。
確認ポイント:Project一覧画面で該当Projectの左側に緑のアイコンが表示されていればSync成功です。赤や黄色の場合はGitHubへの接続に失敗しているので、Source Control URLとCredentialを再確認します。
AWXのJob Template作成
Job Templateは、これまで作ったCredentials・Inventory・Projectを1つにまとめて「実行の単位」にする設定です。AWX APIから呼び出すときも、このJob Templateの番号(ID)を指定します。
AWX UI → 左メニュー「Templates」→「Add」→「Add Job Template」
| 項目 | 値 |
|---|---|
| Name | nw-log-collect-job |
| Job Type | Run |
| Inventory | nw-verify-inventory |
| Project | nw-log-collector |
| Playbook | collect_logs.yml |
| Credentials | nw-verify-bastion-credential |
保存後、「Edit」を開いて「Variables」に以下を追加します。
ansible_ssh_common_args: '-o ProxyJump=ubuntu@172.16.1.166 -o StrictHostKeyChecking=no'
「Save」をクリックします。
AWX APIトークンの発行
次回、Lambdaからこのjob TemplateをAPI経由で起動するために、APIトークンを発行しておきます。
AWX UI → 右上ユーザーアイコン → トークン → トークンを追加
| 項目 | 値 |
|---|---|
| 説明 | lambda-access |
| スコープ | 書き込み |
「保存」をクリックすると、トークンの文字列が表示されます。
このトークンは発行時の1回しか表示されません。閉じてしまうと再確認できず、再発行が必要になるので、必ずこの場でコピーしてメモしておきます。

単体確認
Lambdaからの自動起動を作り込む前に、まずはAWX UIから手動でJobを起動して、Playbookが正しく動くかを確認します。
AWX UI → 「Templates」→「nw-log-collect-job」→「起動(ロケットアイコン)」
実行が始まると「Jobs」画面に遷移し、リアルタイムでログが流れます。
以下のように表示されていれば成功です。
PLAY [Collect Cisco device logs] **********************************
TASK [Run show commands] **********************************
ok: [devnetsandboxiosxec9k.cisco.com]
TASK [Upload to S3] **********************************
ok: [devnetsandboxiosxec9k.cisco.com]
PLAY RECAP **********************************
devnetsandboxiosxec9k.cisco.com : ok=3 changed=1 unreachable=0 failed=0
failed=0になっていることと、unreachable=0になっていることがポイントです。unreachableが1以上の場合は踏み台経由のSSH接続自体に失敗しているので、Inventoryのansible_ssh_common_argsや踏み台EC2のセキュリティグループを再確認します。
続いてS3側も確認します。
S3コンソール → nw-verify-logsバケット → logs/フォルダ
devnetsandboxiosxec9k.cisco.com_(日時).log のようなファイルが作成されていれば成功です。中身を開いて===== show version =====のようにコマンドごとに区切られたログが入っていることを確認します。
しっかりログが取れてること確認できました!
うまくいかない場合の見方
| 症状 | 考えられる原因 | 確認ポイント |
|---|---|---|
| Job自体が起動しない | Job Templateの設定不備 | Inventory/Project/Credentialsが正しく紐づいているか |
| unreachableになる | 踏み台経由のSSH接続失敗 | nw-verify-bastion-credentialのSSH秘密鍵、ProxyJumpの記述、踏み台EC2のSG・起動状態 |
| failedになる(showコマンドでエラー) | Cisco機器側の認証失敗 | Inventoryホスト変数のansible_user/ansible_password(DevNet Sandboxは定期的にリセットされる) |
| S3にアップロードされない | IAM権限不足 | AWXが動いているEC2のIAMロールにS3書き込み権限があるか |
| Project同期が失敗する(赤アイコン) | GitHub接続失敗 | github-credentialのPAT有効期限、Source Control URLのタイプミス |
まとめ
今回はAWXとGitHubを連携させ、Playbookを実行してCisco機器のログをS3に保存するところまで実装しました。
AWXの認証情報周りがうまくいかず直書きになってしまったのでいい方法ないか模索しようと思います。
一旦、AWX単体では「NW機器のログをS3に自動収集する」ところまで準備が整いました。
次回はLambda連携編として、ServiceNowへのインシデント起票と同じタイミングでLambdaからAWX APIを呼び出し、障害検知からログ収集までを完全に自動化する部分を実装していきます。





