Ansibleでロードバランサのアップグレードを自動化してみた
こんちわ、今回はF5の自動化をしてみました。
設計と概要
今回の目的は主に多様性です。
HAであろうが、プラットフォームがなんであれ、複数のF5をシームレスにアップグレードできるプレイが目標となってます。
必要条件:
■アップグレード用のISOはパブリッククラウド上のストレージに存在し(今回はAzure Blob)、アップグレード時にF5がプルする。
■F5のライセンスリアクティベーションはライセンスサーバー経由
■上記の動作などF5がインターネット接続を必要とする場合、プロキシ経由で通信を確保する(もちろん、プロキシなしでの接続も対応要)
■アップグレード作業はAnsibleにて、エンドツーエンドで行われる
■対象のF5が隔離ネットワークに存在する場合、踏み台経由での接続も要対応
■対象のF5がHAでも冗長なしでも1つのプレイブックでシームレスに要対応
では、動作とプレイブックの解説です
Repo: https://gitlab.com/knagatori/ansible-f5-upgrade
Requirement: https://galaxy.ansible.com/f5networks/f5_modules
主にこちらからアイディアとヒントいただきました:
ディレクトリはこんな感じ:
bigip
├── bigip_upgrade.yaml
├── host_vars
│ └── samplelb.yaml
├── roles
│ ├── bigip_cleanup_image
│ ├── bigip_download_image
│ ├── bigip_license_reactivate_active
│ ├── bigip_license_reactivate_standby
│ ├── bigip_software_activation_active
│ ├── bigip_software_activation_standby
│ ├── bigip_traffic_failover
│ └── bigip_ucs_backup
メインのプレイはこんな感じです:
---
- name: Upgrade software on F5
hosts: bigip
gather_facts: no
vars_prompt:
- name: ansible_user
prompt: Input F5 TMUI Username
private: no
- name: ansible_password
prompt: Input F5 TMUI Password
private: yes
unsafe: yes
pre_tasks:
- name: Obtaining HA Information
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "cat /var/prompt/cmiSyncStatus"
wait_for:
- result[0] contains Standalone
- result[0] contains In Sync
register: result
any_errors_fatal: true
delegate_to: localhost
- name: Setting facts
set_fact:
ha_info: "{{ result }}"
cacheable: yes
- name: Obtaining HA State
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "cat /var/prompt/ps1"
wait_for:
- result[0] contains Active
- result[0] contains Standby
register: result
any_errors_fatal: true
delegate_to: localhost
- name: Setting facts
set_fact:
ha_state: "{{ result }}"
cacheable: yes
roles:
- bigip_download_image
- bigip_ucs_backup
- bigip_license_reactivate_standby
- bigip_software_activation_standby
- bigip_traffic_failover
- bigip_license_reactivate_active
- bigip_software_activation_active
- bigip_cleanup_image
1. ISOイメージのダウンロード
まずはISOイメージをF5にアップロードする所からです。
Ansibleモジュールでコピー専門のものがありますが、ローカルシステムからのみの転送となるので、スケーラブルなものを書くことが難しかったです。
また、delegate_toによる踏み台サーバの使用を考えると、そこら中にISOファイルを散蒔く必要があったので、これはボツにしました。
結局F5のbash上のcurlを使ってパブリッククラウドに存在するファイルを落すで完結してます。
- name: Downloading the image file
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c 'cd /shared/images && curl -L --proxy {{ upstream_proxy }}:{{ upstream_proxy_port }} -o {{ image }} {{ azure_sas_image }}'
retries: 1
ignore_errors: true
delegate_to: {{ jumphost }}
問題点:SASのURI形式上エスケープ文字が発生する為、URIをvarとして使用する場合、埋め込みが必要になります。
tmshでのテストでは?が問題になったのですが、Ansibleからのvarハンドリングはまたそれと異り、結論プレイブック上では&のみ埋め込む事をワークアラウンドにしてます。
サンプルSAS:https://hogehoge.blob.core.windows.net/others/BIGIP-14.1.4.5-0.0.7.iso?sp=r&st=2022-02-01T01:18:59Z&se=2022-02-01T09:18:59Z&spr=https&sv=2020-08-04&sr=b&sig=xOxO1ST1%2FP3w48DpsytcKQmpLhYDNEFMuZ3Mf37PgLE%3D
プレイブック上のSAS:https://hogehoge.blob.core.windows.net/others/BIGIP-14.1.4.5-0.0.7.iso?sp=r\&st=2022-02-01T01:18:59Z\&se=2022-02-01T09:18:59Z\&spr=https\&sv=2020-08-04\&sr=b\&sig=xOxO1ST1%2FP3w48DpsytcKQmpLhYDNEFMuZ3Mf37PgLE%3D
2. 冗長ステータスの棲み分け
次に、冗長ステータスの確認です。
極力ダウンタイム無しでアップグレードを実行したい(また、通常の追加作業などでも、HA間でシンキングしているのであれば、Activeのみへの変更でOK)ので、これはどうしても必要でした。
あいにく、既存のfactで利用できるものがなかったので、またbigip_commandに活躍してもらいました。
ここでは、/var/prompt/ps1と、cmiSyncStatusでのアウトプットをカスタムのfactとして登録し、それを利用してます。
ちなみに、シンキングが壊れている場合はfailします。
- name: Obtaining HA Information
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "cat /var/prompt/cmiSyncStatus"
wait_for:
- result[0] contains Standalone
- result[0] contains In Sync
register: result
any_errors_fatal: true
delegate_to: localhost
- name: Setting facts
set_fact:
ha_info: "{{ result }}"
cacheable: yes
- name: Obtaining HA State
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "cat /var/prompt/ps1"
wait_for:
- result[0] contains Active
- result[0] contains Standby
register: result
any_errors_fatal: true
delegate_to: localhost
- name: Setting facts
set_fact:
ha_state: "{{ result }}"
cacheable: yes
スタンドアローン機は必然的にActiveになるので、どっちでも適用できるようになります。
例えばスタンバイ機のみでの実行タスク:
- name: test cmd
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "echo $HOSTNAME"
delegate_to: localhost
when: ansible_facts['ha_state']['stdout'][0] == "Standby"
3. UCSコンフィグのバックアップ
これはAnsibleのオフィシャルモジュールで簡単にできました。
- name: Preflight work - backing up to UCS
bigip_ucs_fetch:
create_on_missing: yes
src: "{{ chg }}_{{ private_ip }}_pre-upgrade.ucs"
dest: "{{ backup_loc }}/{{ chg }}_{{ private_ip }}_pre-upgrade.ucs"
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
delegate_to: "{{ jumphost }}"
4. ライセンスのリアクティベーション
コレ用:https://support.f5.com/csp/article/K7727
- name: Obtaining license information
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- show sys license
register: result
any_errors_fatal: true
delegate_to: localhost
- name: Setting facts
set_fact:
license: "{{ result.stdout_lines[0][2][20:] }}"
- name: Reactivate bigip license using proxy on standby unit
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
commands:
- bash -c "/usr/local/bin/SOAPLicenseClient --proxy {{ upstream_proxy }} --basekey {{ license }} --certupdatecheck"
wait_for:
- result[0] contains New license installed
delegate_to: localhost
when: ansible_facts['ha_state']['stdout'][0] == "Standby" and upstream_proxy is defined
- name: Reactivate bigip license on standby unit
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
commands:
- bash -c "/usr/local/bin/SOAPLicenseClient --basekey {{ license }} --certupdatecheck"
wait_for:
- result[0] contains New license installed
delegate_to: localhost
when: ansible_facts['ha_state']['stdout'][0] == "Standby" and upstream_proxy is not defined
- name: Sleeping
pause:
minutes: 5
5. イメージのインストールとアクティべーション
こちらもオフィシャルで。。。
Active機とStandby機でrolesに分けてます。
下記はStandby用:
- name: Installing and activating new image on standby unit
bigip_software_install:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
timeout: 3200
image: "{{ image }}"
volume: "{{ volume }}"
state: activated
delegate_to: "{{ jumphost }}"
when: ansible_facts['ha_state']['stdout'][0] == "Standby"
any_errors_fatal: true
- name: Sleeping
pause:
minutes: 10
- name: Confirming device status of standby unit before continuing
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c "cat /var/prompt/ps1"
wait_for:
- result[0] contains Standby
retries: 12
interval: 10
register: result
any_errors_fatal: true
delegate_to: "{{ jumphost }}"
when: ansible_facts['ha_state']['stdout'][0] == "Standby"
- name: Sleeping
pause:
minutes: 5
6. 後片付け
bash -c 'rm -f /shared/image/{{ image }}でアップロードしたイメージを消去します。
個人的にこれは今後オフィシャルのを使ってabsentフラグでもいいかな。
---
- name: Cleaning up image after upgrade
bigip_command:
provider:
server: "{{ private_ip }}"
user: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
warn: no
match: "any"
commands:
- bash -c 'rm -f /shared/images/{{ image }}'
- bash -c 'rm -f /shared/images/{{ image }}.md5'
any_errors_fatal: true
delegate_to: "{{ jumphost }}"
今後の課題
■APIでSASのURIを取得する際に、Ansibleでそのまま利用できる形式にテイラーする必要性がある。他の外部ストレージもテスト要。
■Curl経由でのISOダウンロードがどうしても不可能(ファイアーウォール等の影響で)な場合、ローカルからISOをアップロードするプレイも必要。
■オフィシャルのモジュールを使った際、イメージのインストールとアクティべーションを別で対応するのが不可能(?)みたい。カスタムコマンドで対応すべきか。
■アップグレード前後のステートチェックにどうインタグレートするか。
Discussion