こんにちは。インフラの奥村です。
最近、業務でインフラ運用業務の改善活動をさせて頂いています。
僕が行った改善活動の一つ
「ディスク拡張のコマンド化」について書いていこうと思います。
以前までは
- Web GUIで仮想マシンのディスクを追加し
- 対象のマシンにSSH等でアクセスし
- コマンドを打ち込む。
という作業を行なっていました。この作業を短縮するべくansibleを書きました。
環境
- 対象: vSphere仮想マシン
必要な工程
- 仮想マシンのディスク拡張(デバイス追加)
- パーティションの拡張
- LVM領域の拡張
- ファイルシステムの拡張
以上4工程をansible化しました。shellの利用が多くなっているのは妥協してしまっています。
必要無さそうな条件分岐等がありますが、これはディスク拡張のパターンとして
- デバイスを追加するパターン
- ディスクの容量を拡張するパターン
があるためです。 今回はデバイスを追加するパターンにフォーカスを当てて書きますので、余分な部分はカットしています。
ファイル群
- ディレクトリ構造
.(DISK_CHANGE_ROOT) ├── ansible │ ├── disk_change.yml │ └── roles │ └── disk │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ └── disk_add.yml │ └── templates │ └── disk_add_cmdline.j2 ├── govc_commands │ └── disk_add └── scripts └── exec_disk_add.py
playbook
- disk_change.yml
--- - hosts: default become: True vars: execute_disk_add: True roles: - disk
- main.yml
- name: generate govc command #テンプレートからgovcのコマンドを生成する become: False template: src: disk_add_cmdline.j2 dest: "{{ playbook_dir }}/../govc_commands/disk_add" mode: 0755 delegate_to: 127.0.0.1 when: ip is defined - name: include task for disk_add include: disk_add.yml when: execute_disk_add == True
- disk_add.yaml
--- - name: execute disk_add #govcコマンドを実行する become: False local_action: shell ./disk_add args: chdir: "{{ playbook_dir }}/../govc_commands/" run_once: True - name: print disk info #現在のデバイスの状態をファイルとして書き出しておく parted: device: /dev/{{ device_name }} state: info unit: KiB register: result - name: save disk info to file copy: content: "{{ result }}" dest: /home/okumura/export.txt - name: create partition parted: device: /dev/{{ device_name }} number: 1 flags: ["lvm"] state: present part_end: 100% ignore_errors: True - name: exec parted command because ansible module failer #partedモジュールでうまくいかなかったのでpartedコマンドで対応 shell: parted -s -m -a optimal /dev/{{ device_name }} set 1 lvm ignore_errors: True - name: set already_pv flag #追加するデバイスがすでにPhysical Volumeにないか確認する shell: pvdisplay | grep {{ device_name }}1 register: already_pv ignore_errors: True - block: - name: pvcreate shell: pvcreate /dev/{{ device_name }}1 - name: resize volume group shell: vgextend VolumeGroup /dev/{{ device_name }}1 - name: resize logical volume shell: lvextend -l +100%FREE /dev/VolumeGroup/LogicalVolume - name: resize filysystem filesystem: fstype: ext4 dev: /dev/VolumeGroup/LogicalVolume resizefs: yes when: - ansible_distribution == "CentOS" - ansible_distribution_major_version == "6" - name: resize filysystem filesystem: fstype: xfs dev: /dev/VolumeGroup/LogicalVolume resizefs: yes when: - ansible_distribution == "CentOS" - ansible_distribution_major_version == "7" when: already_pv.rc == 1
テンプレートファイル
- disk_add_cmdline.j2
{% for host in ip %} #呼び出すときのIPの数でループ govc vm.disk.create -vm.ip={{ host }} -size {{ disk_size }}G -name={{ device_name }} -ds=datastore {% endfor %}
こんな感じです。
流れを説明すると
- disk_add_cmdline.j2というテンプレートファイルをもとにして、localhost宛にgovcのスクリプトを作成する。
- パーティションを切る
- すでにデバイスがないか判断する
- 無い場合はphysical volumeを作成し、Volume Groupの拡張、Logical Volumeの拡張を行なう
- ディストリビューションを判断し、デフォルトのファイルシステムで拡張
という流れです。
192.168.0.1のホストのディスクサイズを20GB拡張したいときに呼び出すansibleは
ansible-playbook -i inventory/hosts --extra-vars '{"ip": [192.168.0.1], "disk_size": 20, "device_name": sdb}' ansible/disk_add.yml
!?
長すぎるじゃないですか!シングルクウォートとダブルクォートが混在しているコマンドを誰が好んで実行するんですか! ってなりますよね。私もなりました。
ということで、今回はこのコマンドを呼び出すためのスクリプトも作ってみました。 内容としては 受け取った引数を元にansibleのコマンドを作り出し、それを実行するというまぁ、うん、なスクリプトです。
- exec_disk_add.py
#!/usr/bin/python # -*- coding: utf-8 -*- import sys import subprocess import os import argparse def create_inventory(iplist,env_path): f = open("" + env_path + "/inventory/pre_hosts","w") inventory_text = "[default]\n" for ip in iplist: inventory_text = inventory_text + ip + "\n" f.write(inventory_text) f.close() def create_cmd_args_for_add(iplist,size,device): cmd = '{"ip": [' + ",".join(iplist) + '], "disk_size": ' + size+ ', "device_name": ' + device + '}' return cmd def create_cmd(cmd_args,env_path): cmd = "ansible-playbook -i " + env_path + "/inventory/pre_hosts --extra-vars '" + cmd_args + "' " + env_path + "/ansible/disk_add.yml" return cmd def main(): parser = argparse.ArgumentParser(description='this script extends the disk.') parser.add_argument("-i","--ip", help = "target ips. This option is required", required=True, nargs="*") parser.add_argument("-s","--size", help = "target disk size. This option is required", required=True) parser.add_argument("-d","--device", help = "choice device. This option is required", required=True) parser.add_argument("-p","--printif", help = "print command", action="store_true", default=False,) cmd_arg = parser.parse_args() create_inventory(cmd_arg.ip,os.environ.get('DISK_CHANGE_ROOT')) cmd = create_cmd(create_cmd_args_for_add(cmd_arg.ip,cmd_arg.size,cmd_arg.device),os.environ.get('DISK_CHANGE_ROOT')) print(decided_cmd) if cmd_arg.printif == True else subprocess.call(decided_cmd, shell=True) if __name__ == "__main__": main()
上のディレクトリ構造の「.」の部分を「DISK_CHANGE_ROOT として環境変数に登録すれば
./exec_disk_add.py -i 192.168.0.1 -s 20 -d sdb
これで呼び出せるようになりました。
まとめ
最近「Yak Shaving(ヤクの毛を刈る)」という言葉を知りました。
「ある問題を解決しようとしたら、別の問題に直面し、その問題を解決しようとしたら、また別の問題に直面し・・・」
というようになることだそうです。(気になる方は調べてください)
改善活動をしていると、こういう状況に出くわすことが多いと思います。
ですが、改善活動の場合は、出てきた問題を一つ一つ潰していくのが理想だと私は思います。
最後までご覧頂き、ありがとうございます。