ansibleでディスクを拡張してみる

こんにちは。インフラの奥村です。

最近、業務でインフラ運用業務の改善活動をさせて頂いています。
僕が行った改善活動の一つ
「ディスク拡張のコマンド化」について書いていこうと思います。

以前までは

  1. Web GUIで仮想マシンのディスクを追加し
  2. 対象のマシンにSSH等でアクセスし
  3. コマンドを打ち込む。

という作業を行なっていました。この作業を短縮するべくansibleを書きました。

環境

  • 対象: vSphere仮想マシン

必要な工程

  1. 仮想マシンのディスク拡張(デバイス追加)
  2. パーティションの拡張
  3. LVM領域の拡張
  4. ファイルシステムの拡張

以上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 %}

こんな感じです。

流れを説明すると

  1. disk_add_cmdline.j2というテンプレートファイルをもとにして、localhost宛にgovcのスクリプトを作成する。
  2. パーティションを切る
  3. すでにデバイスがないか判断する
  4. 無い場合はphysical volumeを作成し、Volume Groupの拡張、Logical Volumeの拡張を行なう
  5. ディストリビューションを判断し、デフォルトのファイルシステムで拡張

という流れです。

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(ヤクの毛を刈る)」という言葉を知りました。

「ある問題を解決しようとしたら、別の問題に直面し、その問題を解決しようとしたら、また別の問題に直面し・・・」

というようになることだそうです。(気になる方は調べてください)
改善活動をしていると、こういう状況に出くわすことが多いと思います。

ですが、改善活動の場合は、出てきた問題を一つ一つ潰していくのが理想だと私は思います。

最後までご覧頂き、ありがとうございます。