【VMware】vSphereのSDKを使って仮想マシーンを作成してみた

f:id:hira98:20200322221955p:plain

【目次】

はじめに

本記事では、仮想化ソフトウェアのVMware社が自社の製品を使った操作の自動化推進のために提供しているSDKを使って、VMインスタンスを作成する方法について解説します。

情報の正確さと網羅性を求めるのであれば、公式ドキュメントをみた方が確実です。

https://docs.vmware.com/jp/VMware-vSphere/6.5/com.vmware.vsphere.cli-sdk-stub.doc/GUID-CCA85057-D570-409F-8B19-034AF1952229.html

...しかし、英語で情報量も多いです。

膨大な英語のドキュメントなんて、なるべく読みたくない!

知りたいのはやりたいことができるかどうか?

やりたいことを日本語でまとめてくれている人はいないのか?

と藁にもすがる思いでググって、役に立つ個人ブログの記事を見つけたときテンションが上がりました。

そんなネットに恩返しがしたくて、久々にブログを書くことにしました。

実現したいこと

VMware ESXiのGUIを操作して作成していた仮想マシーン作成作業の自動化。

ただし、仮想マシーン作成後の初回起動時の設定(IPアドレスの設定など)は自動化の対象外。

実現方法

VMware社は、複数のVMwareESXiサーバーをまとめて管理できるようにvCenterというソフトウェアを提供しています。vCenterはAPIを提供しており、これを呼ぶことで仮想マシーン作成作業を自動化できます。

事前準備として、vCenter用のサーバーを別途用意して、VMwareESXiサーバーを関連づけておく必要があります。(設定方法は省略)

vCenterのAPIについて

vCenterが提供するAPIにはSOAP形式とREST形式の2種類あります。

REST形式は、URLにリソースパスを記載し、GETで設定取得/POSTで設定追加を行うWebAPIの実装方式です。

対してSOAP(ソープ)形式は一昔前に流行ったAPIの提供方式で、独自のXML形式のデータをやりとりして設定取得/追加を行うWebAPIの実装方式です。

SOAP形式と比較してREST形式の方がHTTPに寄せているので、直感的に分かりやすくかつHTTPリクエストを投げているだけなので、開発言語に依存しない実装が可能です。

私としてはREST形式のみで完結したいところですが、現時点だとSOAP形式の方が機能面で優れており、REST形式だと実現できない機能(※1)が多いです。

私が調べた中だと、作成した仮想マシーンと仮想スイッチを接続するための仮想ケーブル(ポートグループ)を追加したり詳細情報を取得する機能はSOAP形式でしか提供されていませんでした。

実装方針

開発言語:Python

開発方針:基本REST形式で、どうしても実現できない機能だけはSOAP形式を使用する。

その他:REST形式のWebAPIの動作検証用にPostman(GUI操作でWebAPIを実行できるツール)を使用する

VMware社提供のモジュール

今回はPythonとPostmanを使って開発するため、次の3つを落としてきます。

なぜPythonモジュールを使うのか?

SOAP形式のAPIってXML形式のデータをHTTPで投げているだけなら、モジュールの使い方を把握するより、自力で実装した方が楽じゃね?」

と疑問に思っていたのですが、よくよく考えると「XMLの記載方法を把握する方がしんどい」ことに後で気づきました。

また、モジュールはvCenterで管理しているリソースを階層構造でいい感じに表現しており、実行できる環境があればデバッグ機能を使って、ログイン→階層構造で整理されたvCenterのリソース取得→ブレイクで処理中断→デバッグコンソールで取得したリソースを漁って、それっぽい処理を見つける。という風にモジュールのドキュメントを読まなくても、お手軽に試行錯誤できるため意外と簡単に使えました。

例として、「ポートグループを追加するサンプルソースから、ポートグループの詳細情報を取得する方法を調べた時の実施方法」について説明します。

サンプルソースhttps://github.com/vmware/pyvmomi-community-samples/blob/master/samples/add_portgroup_to_vswitch.py

main()を抜粋しコメント追加:

# サンプルソース:
def main():
    # 引数取得
    args = get_args()
    
    # ログイン処理
    if args.skip_verification:
        serviceInstance = SmartConnectNoSSL(host=args.host,
                                            user=args.user,
                                            pwd=args.password,
                                            port=443)
    else:
        serviceInstance = SmartConnect(host=args.host,
                                       user=args.user,
                                       pwd=args.password,
                                       port=443)
    # 処理終了時にのログアウト処理が呼ばれるようにする
    atexit.register(Disconnect, serviceInstance)
    
    # vCenterにひもづく全てのVMwareESXiの情報を取得
    content = serviceInstance.RetrieveContent()
    
    # regex_esxiと同じ名前のVMwareESXiの情報だけを取得する。
    hosts = GetVMHosts(content, args.regex_esxi)
    
    # ここにブレイクはって、デバッグコンソールでhostsの中身を見れば、
    # いい感じに情報が取得できる(`・ω・´)キリッ
    
    # ポートグループ追加
    AddHostsPortgroup(hosts, args.vswitch, args.portgroup, args.vlanid)

vCenterでのオブジェクト管理について

vCenterではホスト(VMwareESXiサーバー)、データストア、ネットワーク(ポートグループ)、リソース、仮想マシーンなどにIDを割り振って管理しており、vCenter経由で仮想マシーンを作成する際はIDを指定する必要があります。

既存のオブジェクトの名前からIDを取得するAPIは全てREST形式で実現でき、詳細について後述します。

仮想マシーンを作成するAPI

仮想マシーンの作成はREST形式のAPIで実現できます。

APIの使い方の詳細は以下リンクを参照ください。

http://vmware.github.io/vsphere-automation-sdk-rest/6.7.1/operations/com/vmware/vcenter/vm.create-operation.html

APIの実行例でobj-103と設定されている箇所には、vCenterが割り振ったオブジェクトIDを指定しています。

パラメーターは大量にありますが全て設定する必要はなく、デフォルトから変わるものだけ設定すれば良いです。

最低限の設定が必要なパラメーターを見つける一番簡単な方法は、手動作成した仮想マシーンの設定情報を参考にする方法です。

仮想マシーンの設定情報は、REST形式のAPIを次の手順で実行することで取得できます。

  1. 作成した仮想マシーンのID取得。

    http://vmware.github.io/vsphere-automation-sdk-rest/6.7.1/operations/com/vmware/vcenter/vm.list-operation.html

    →仮想マシーンの一覧を取得するAPI。クエリパラメーターとしてfilter.names.1={仮想マシーン名}を指定することで、任意の仮想マシーンのIDのみを取得することができる。

  2. ID指定で仮想マシーンの詳細情報取得。

    http://vmware.github.io/vsphere-automation-sdk-rest/6.7.1/operations/com/vmware/vcenter/vm.get-operation.html

    URLに仮想マシーンのIDを指定することで、作成済みの仮想マシーンの詳細情報を取得するAPI

さいごに

今の案件に参画して半年。

CCNP取得者の巣窟でニッチなネットワーク製品のAPI調査&検証作業を行う日々に、ただでさえ低い自己肯定感が地に落ちていく状況でした。

そんな中、 縛りプレイから解き放たれたPC環境(Notシンクラ端末)でVMwareAPIを調査&実装していた2週間はとても充実したものでした。

【Python】階層構造になった辞書型データから一部を削除してみた

f:id:hira98:20191125205204p:plain

【結論】

  • 階層構造のデータは再帰関数で扱う(持論)
  • AnsibleやRobot Frameworkとは今後もビジネスパートーナーとして良好な関係を築いていきたい。

【目次】

はじめに

最近の業務内容です。

本題だけ知りたい人はスルーしてください😓

最近、「Python」と「robot framework」で実装された「WebAPIのテスト自動化ツール」を引き継ぎました。

このツールを引き継いで行う作業は次の2つです。

  1. ツールを使っての検証作業。

  2. 新たにWebAPIが追加された際にテストケースを作成する。

テストケースを作成するWebAPIは、「パラメーターを取得するAPI」と「パラメーター取得&設定を行うAPI」の大きく2つに分類されます。

「パラメーターを取得するAPI」は結果がJSON形式なので、どこを見て結果を判断するのかをツールの記載ルールに従って記載します。(例:起動時間を取得するAPIだと、時間が多少ずれるのは想定通りなのでスルーする。)

「パラメーター取得&設定を行うAPI」はGETで取得したデータをDELETEで削除し後にPOSTします。

これらの概要を把握して思ったことは、「GETしたデータを一部スルーする設定を書いたり、GETしたデータをそのままPUTするだけなんて、余裕じゃん😎」です。

しかし、その余裕はスグに打ち砕かれました😵

引き継いで初っ端に追加するテストケースがイレギュラーだったからです。

「パラメーター取得&設定を行うAPI」ですが、GETしたデーターの一部を除去してPOSTしないといけません。しかし、既存ロジックにそのようなテストケースはありませんでした。

既存のテストケースは全て「Robot Framework」で書かれています。

私も空気を読んで「Robot Framework」で実装しようとしたのですが「まぁーうまくいかない😱」。

Pythonだとこれでいけるんだけどなー😰」

「Robot Frameworkだとどう書けばいいんだ😭」

...ここで前の案件で「Pythonを捨ててAnsibleで突き進んだ結果みた地獄」がトラウマとして蘇ります🥶

下手に空気を読んで自分の意見を殺して突き進んだところで、待ち受けているのは底なしの後悔🤮

「Robot Framework」と「Python」どちらを選んでも過酷な道のりが待ち受けているのかもしれません。

ただ私は、どんな苦難も好きであれば乗り越えていけると信じています😤

僕が好きなのは「Robot Framework🤖」ではなく「Python🐍」。

Python🐍」お前と見る地獄なら大歓迎だぜ😘。

やりたいこと←ここからが本題

階層構造のjsonデータから、指定したデータだけを削除した結果を得たい。

  • 引数:
    • JSON形式のデータ
    • 削除したいJSONパス
      • 複数のJSONパスを指定する場合は"aaa","bbb"のようにカンマ区切りで指定する。
      • 階層が深いデータを削除したい場合は"aaa.aaa_1"のようにコンマ区切りで指定する。
  • 戻り値:
    • 引数で渡された「削除したいJSONパス」のデータを削除したJSON形式のデータ

実行結果サンプル

引数:JSON形式のデータ

{
    "aaa":{
    "aaa_1":"aaa_data1",
    "aaa_2":"aaa_data2", 
 },
  "bbb":"bbb_data",
  "ccc":{
    "ccc_1":{
        "ccc_2":"ccc_data",
    }
}

引数:削除したいJSONパス

["aaa.aaa_1", "bbb"]

戻り値:

{
    "aaa":{
    "aaa_2":"aaa_data2",
 },
  "ccc":{
    "ccc_1":{
        "ccc_2":"ccc_data",
    }
}

Pythonコード

import copy

def del_dic(data, keys):
    if len(keys) == 1:
        del data[keys[0]]
        return
    else:
        temp_key = keys.pop(0)
        temp_data = data.pop(temp_key)
        del_dic(temp_data, keys)
        data[temp_key] = temp_data

def del_dic_main(dic_data, del_list):
    work_data = copy.copy(dic_data)
    del_keys = copy.copy(del_list)
    del_keys.sort(reverse=True)
    for key in del_keys:
        key_list = key.split(".")
        if len(key_list) >= 1:
            del_dic(work_data, key_list)  
    return work_data

if __name__ == "__main__":
    test_data1 = {
        "aaa":{
            "aaa_1":"aaa_data1",
            "aaa_2":"aaa_data2",
        },
        "bbb":"bbb_data",
        "ccc":{
            "ccc_1":{
                "ccc_2":"ccc_data",
            }
        }
    }
    print(f"before: {test_data1}")
    test_data1 = del_dic_main(test_data1, ["aaa.aaa_2","bbb"])
    print(f"after: {test_data1}")

実行結果

before: {'aaa': {'aaa_1': 'aaa_data1', 'aaa_2': 'aaa_data2'}, 'bbb': 'bbb_data', 'ccc': {'ccc_1': {'ccc_2': 'ccc_data'}}}
after: {'ccc': {'ccc_1': {'ccc_2': 'ccc_data'}}, 'aaa': {'aaa_1': 'aaa_data1'}}

さいごに

ネットワークもいいけど、やっぱりこういうロジック考える方が好き。

【Ansible】気づいたら文字列型に変換されていた件について

f:id:hira98:20190916162123p:plain

【結論】

  • リスト型で定義した変数をwith_itemsで順に取り出して、include_tasksvarsを使ってsub_taskに渡しても型変換されない。

  • しかし、同じ処理を辞書型で実装すると、sub_taskに渡した変数は自動で文字列型に変換される。

【目次】

はじめに

前回全力でふざけてしまったので、今回は淡々と試したAnsibleソースと結果だけを書いていこうと思います。

リスト型で定義した変数をsub_taskに渡した場合

試したソース

【main_task.yml】

---
- hosts: localhost  # 自分自身に接続する
  tasks:
  - set_fact:
     sample_data_list:
       - "10"
       - 10
       - ""
       - 
       - null

  - name: main_task
    debug:
      msg: "(var:{{item}} type:{{item | type_debug}})"
    with_items:
      - "{{ sample_data_list }}"

  - include_tasks: sub_task.yml
    vars:
      data: "{{ item }}"
    with_items:
      - "{{ sample_data_list }}"

【sub_task.yml】

---
- name: sub_task
  debug:
    msg: "(var:{{data}} type:{{data | type_debug}})"

実行結果

main_task.ymlでの出力結果

f:id:hira98:20190916162210p:plain

sub_task.ymlでの出力結果

f:id:hira98:20190916162222p:plain

main_taskとsub_taskでは同じ変数を出力しているため、当然値も型も同じになります。

辞書型で定義した変数をsub_taskに渡した場合

最初に説明したリスト型との差分は、データ定義を辞書型に変えただけです。

試したソース

【main_task.yml】

---
- hosts: localhost  # 自分自身に接続する
  tasks:
  - set_fact:
      sample_data_dic:
        - { "data":"10" }
        - { "data":10   }
        - { "data":""   }
        - { "data":     }
        - { "data":null }

  - name: main_task
    debug:
      msg: "(var:{{ item.data }} type:{{item.data | type_debug}})"
    with_items:
      - "{{ sample_data_dic }}"

  - include_tasks: sub_task.yml
    vars:
      data: "{{ item.data }}"
    with_items:
      - "{{ sample_data_dic }}"

【sub_task.yml】

---
- name: sub_task
  debug:
    msg: "(var:{{data}} type:{{data | type_debug}})"

実行結果

main_task.ymlでの出力結果

f:id:hira98:20190916162237p:plain

sub_task.ymlでの出力結果

f:id:hira98:20190916162249p:plain

リスト型ではmain_taskとsub_taskでは同じ変数を出力しているため、当然値も型も同じになります。

しかし、辞書型ではmain_taskでは定義通りに出力されますが、sub_taskでは文字型に自動変換されるようです。

さいごに

Ansibleとうまく付き合うコツは、Ansibleの思想を理解することではなく、思想に基づいて実装された結果を淡々と記録して証跡を残して、同じ過ちを2度と繰り返さないことだと最近気づきました。

【Ansible】AnsibleTowerしか使えない開発環境でサンプルソースを効率よく試す方法

f:id:hira98:20190915191410p:plain

【結論】

  • AnsibleTowerにはSurvery機能がある。

  • この機能を使うことで、ジョブテンプレート実行時に任意の値を渡すことができる。

  • 渡された値によって、読み込む.ymlファイルを可変にすることで効率よくサンプルソースを試せる。

  • Ansibleではfomart()を使うことで、C言語sprintf()と同じ文字列置換を行える。

【目次】

はじめに

CLIで操作可能なAnsibleではなく、GitLabにソース投入してAnsibleTowerで実行する方法しかない開発環境に身を置いている私が、効率よくサンプルソースを試すために考えた手順を紹介します。

Ansibleでサンプルソースを試す場合

CLI環境のAnsibleでサンプルソースを試す手順は以下になります。

【手順】

  1. サンプルソースを作成する。
  2. コマンドを叩いてサンプルソースを実行する。

以上です。これ以上でも以下でもありません。

AnsibleTowerでサンプルソースを試す場合

AnsibleTowerはAnsibleのソースをGUIで実行するためのインタフェースでしかなく、ソースはGitLab等のソース管理ツールで管理する必要があります。

よって、AnsibleTowerでサンプルソースを動かすには、GitLabにソースを投入し、AnsibleToweで実行した時にGitLab上のどのソースを実行するかを設定してあげる必要があります。

事前準備

  1. GitLabにサンプルソースを格納するための任意のプロジェクトを作成する。

  2. AnsibleTowerでプロジェクト作成して、GitLabに作成したプロジェクトを紐づける。

    詳細な手順は以下参照

  https://docs.ansible.com/ansible-tower/3.2.4/html_ja/userguide/projects.html

サンプルソース作成手順

  1. GitLabに作成した任意のプロジェクトにサンプルソースをcommitする。

  2. AnsibleTowerを操作して、サンプルソースを動かすためのジョブテンプレートを作成する。

    下図はジョブテンプレート作成するための設定画面です。

    [1]は事前準備2で作成したプロジェクトをリストから選択します。

    [2]ではプロジェクト(GitLabと連携)に紐づいたソース一覧(※1)から、このジョブテンプレートを実行した時に実行したいソースを選択します。

    ※ Ansibleのコーディング規約に違反している(拡張子が.ymlでない、先頭行が---でないetc...)ファイルはGitLabに登録されていたとしても、一覧に表示されません。

    f:id:hira98:20190915191704p:plain 引用:https://docs.ansible.com/ansible-tower/3.2.4/html_ja/userguide/job_templates.html

  3. 作成したジョブテンプレートを実行する。

AnsibleTowerしか動かせない環境で効率よくサンプルソースを試す方法

Before_1

真っ先に思いついた方法ですが、チームみんなで共有しているAnsibleTowerのジョブテンプレートが増えまくるので却下しました。

f:id:hira98:20190915191438p:plain

Before_2

AnsibleTowerについて無知だった私は、以下の方法でサンプルソースを試していました。

サンプルソースを作成するたびに、ジョブテンプレートを設定し直す必要があり手間です。。

f:id:hira98:20190915191451p:plain

After

AnsibleTowerのジョブテンプレートにはSurvery機能があります。この機能を使うことで、ジョブテンプレート実行時に任意の値を渡すことができます。

Survery機能でワイが入力したidによって、どのサンプルソースを実行するかを判断する「サンプルソースxx」という仲介役をかましてあげることで、ファイルを追加するだけでサンプルソースを動かすことができるようになります。

f:id:hira98:20190915191507p:plain

効率よくサンプルソースを動かすためのyml

以下になります。

---
- hosts: localhost  # 自分自身に接続する
  tasks:

  # 実際はAnsibleTowerのSurvery機能で宣言される変数
  - name: Survery入力で定義された変数
    set_fact:
      task_id: 01

  - name: タスク名のフォーマット指定
    set_fact:
      task_name_format: "sample_%02d.yml"

  - name: 実行するタスク名確定    
    set_fact: 
      task_name: "{{ task_name_format | format(task_id) }}"

  - debug: msg="{{task_name}}"

  - name: "{{task_name}}を実行する"
    include_tasks: "{{ task_name }}"

[task_name_formatで指定したサンプルソース命名規則] + [surveryで入力したtask_id]から、include_tasksで実行する.ymlを決めるようにすることで実現しています。

ジョブテンプレートでSurveryを設定する方法は以下を参照ください。

https://docs.ansible.com/ansible-tower/3.2.4/html_ja/userguide/job_templates.html#surveys

参考

https://docs.ansible.com/ansible-tower/3.2.4/html_ja/userguide/index.html#ansible-tower-user-guide-versionshortest

さいごに

なぜ私はこのようなブログを書いているのだろう??

DockerかVagrantをローカルPCにインストールできる環境であれば、やる必要もない効率化の方法について考えているのだろう??

と自問自答してて、改めて気づかされる。。

現在の自分は、開発環境が優遇されたモダンなIT企業ではなく、好きなツールをインストールできなシンクラ端末という無人島みたいな開発環境でサバイバル生活をしているのだと。。

【Ansible】"yes"という文字列を変数に格納したのに…

f:id:hira98:20190915225211p:plain

【結論】

  • Ansible嫌いな人間が、全く理解できないAnsibleの仕様をおもしろおかしく書いたブログになります。

【目次】

はじめに

イリュージョニストAnsibleのマジックショー🎉🎉」

多少のプログラミング経験をお持ちのそこのあなた。

今宵あなたはAnsibleが織りなす、不思議な現象に度肝を抜かれることでしょう😎

Pythonで書いた場合

Ansibleによるイリュージョンをお見せする前に、まずは同じ処理をPythonで書いた場合について解説します。

以下のコードを実行しても、宣言した4種類の変数は全く違う値を格納しているので「全て同じ値です。」と出力されることはありません。

それどころか、var_yes = yesyesという定義はないと怒られます。

理由はboolean型として定義されているのはTrueFalseだけだからです。

また、大文字小文字も見ているのでvar_true = Truevar_true = trueと書いても怒られます。

# python
str_yes = "yes"
str_true =  "True"
var_true = True
var_yes = yes

if str_yes == str_true == var_true == var_yes:
  print("全て同じ値です。")

これを改善して次のようなロジックに変えたとしても、今度はif文で怒られます。

str_yesstr_trueは文字列型で、var_trueはboolean型になり、異なる型の変数を比較しようとしているためです。

# python
str_yes = "yes"
str_true =  "True"
var_true = True

if str_yes == str_true == var_true:
  print("全て同じ値です。")

さらに改善して、次のようなロジックに変えたとしても、「全て同じ値です。」とは出力されません。

なぜなら、"yes""True"は異なる文字列だからです。

# python
str_yes = "yes"
str_true =  "True"
if str_yes == str_true:
  print("全て同じ値です。")

Ansibleで書いた場合

だらだらと分かりきっているPythonコードを解説されて退屈されているプログラマーの皆さん。

お待たせしました。

以下のソースがAnsibleだからこそ書けるマジックソースです。

---
- hosts: localhost  # 自分自身に接続する
  tasks:

  - set_fact:
      str_yes: "yes"
      str_true: "true"
      var_true: true
      var_yes: yes

  - debug:
      msg: "全て同じ値です。"
    when: (str_yes == str_true == var_true == var_yes)

Pythonコードと全く同じ処理をAnsibleで書いています。

「異なる値を格納した4つの変数が全て同じになるわけがないだろ😤」

最初にこのコードを見たときは私も同じ感想を持ちました。

しかし、実行してみるとあら不思議「全て同じ値です。」と出力されます🎉

f:id:hira98:20190915225303p:plain

さらに次のようなコードも追加して、変数に格納された値と型も出力してみましょう。

---
- hosts: localhost  # 自分自身に接続する
  tasks:

  - set_fact:
      str_yes: "yes"
      str_true: "true"
      var_true: true
      var_yes: yes

  - debug:
      msg: "全て同じ値です。"
    when: (str_yes == str_true == var_true == var_yes)

  - name: "str_yes"
    debug: 
      msg: "val: {{str_yes}}  type: {{ str_yes | type_debug}}"

  - name: "str_true"
    debug: 
      msg: "val: {{str_true}}  type: {{ str_true | type_debug}}"

  - name: "var_true"
    debug: 
      msg: "val: {{var_true}}  type: {{ var_true | type_debug}}"

  - name: "var_yes"
    debug: 
      msg: "val: {{var_yes}}  type: {{ var_yes | type_debug}}"

果たしてどのような結果が出力されるでしょうか?

気になる実行結果はCMの後で🤪

.

.

.

.

.

.

.

.

どんな名曲も私たちの色に染めてみせます!!実力派アカペラ男女ユニット「Groovy groove」のカバー曲

多くは語りません。一度その動画を見れば、綺麗な歌声に、アカペラならではのアレンジにあなたも一瞬で虜になることでしょう。

Youtubeに無料で公開されているので、もし時間があるのであれば、ぜひ一度聞いて見てください。

【アカペラ】もらい泣き - 一青窈|Cover by Groovy groove

【アカペラ】白日 - King Gnu|Cover by Groovy groove

【アカペラ】原宿いやほい - きゃりーぱみゅぱみゅ|Groovy groove

【アカペラ】デジモンメドレー(Digimon Medley)|Groovy groove

【アカペラ】真夏の夜の匂いがする - あいみょん|Cover by Groovy groove

「Groovy groove」のYoutubeチャンネルは以下になります。

https://www.youtube.com/channel/UC2ZQJqk4jVeI1e6jk2f3RkQ/videos

※ 本CMは個人の趣味で勝手に提供しているだけで、「Youtube」や「Groovy groove」との利益関係は一切ございません。

.

.

.

.

.

.

.

.

さあ、果たして「異なる値を格納した4つの変数」はどのような結果を出力するのでしょうか。

その結果が以下になります。

f:id:hira98:20190915225320p:plain

なんと、なんと、同じ値と型が出力されているではありませんかー。

これぞ、Ansiblだからこそ起こせる、プログラマーには全く理解できないイリュージョンです。

さいごに

全力でAnsibleをネタにしたブログを書いてしまった😇

Ansible😘な人に叩かれたらどうしよう🥶

【Ansible】君と過ごした2ヶ月を振り返って思うこと

f:id:hira98:20190909005614p:plain

【結論】

  • 拘りを捨てたプログラマーは3流以下(持論)
  • 「はい」以外の「すぐには答えを出せないので5分ほどお時間ください。」などを言えるようにならないと、自分で自分の首を絞める羽目になる。
  • 日本語を使ったコミュニケーションで正確な情報伝達を行うのは難しく、お互いに自分の都合のいいように解釈してしまう。
  • 自力でコードを書けない自分は、コードレビューが最終手段となる案件には二度と関わりたくない。

【目次】

はじめに

本記事は、私がSES系の会社に転職しAnsibleと出会ってから過ごした2ヶ月を振り返ってまとめた記事です。

わざわざブログにまとめるぐらいだから、Ansible惚気ブログだと思われるかもしれませんが、決してそんなことはありません。

会社に身を任せ、プロジェクトの流れに身を任せた結果、Ansible向きでない作業をAnsibleで行い、後戻りできない状況に追い込れ。

とにかく動くようにするために、自分が大事にしている「DRY至上主義」をへし折って、心を無にして突き進むが、それでも作業は遅延。

「いつ終わるの?」と圧をかけてくる人が「開発者側の取りまとめ」→「設計担当」→「設計担当の上司」と変化していき。

最終的には、設計担当の上司に「これは一度コードレビューした方がいいかな」と切り札を切られるまでに追い詰められる。

そんな状況にまで陥った自分への戒めや、今後の人生に生かすための教訓を得るためにまとめました。

因みに、詰問されている最中は「切り札がコードレビューってどいうこと??」っていう質問を我慢するのに必死でした。。

Ansibleと相見えることはないと思っていたニート時代

私が初めてAnsibleの存在を知ったのは、会社を辞めてニート時代だった頃に読んだ、雑誌に載っていたAnsible特集漫画です。

Ansibleの印象については以下ブログにまとめいるため割愛します。

hira98.hatenablog.com

当時はまさか自分がAnsibleと付き合う羽目になるとは思いもしていなかったため、違う世界の話を面白半分で聞いている感覚でした。

ニートを卒業して社畜

組み込み系の前職を辞めてから1年間は自分の貯金に寄生するニート生活を満喫していました。

  • 自転車にテント積んで1ヶ月の北海道旅行をしたり
  • 厄災ガ○ンを倒してハイ○ルの世界を救うために冒険したり
  • 「赤い帽子の配管工」や「ピンクボールの食欲魔人」や「ハイ○ルを救った勇者」達と乱闘騒ぎをしたり
  • 某プログラミングスクールに通って、快適なPC環境で自由気ままにプログラミング学習したり。

ニートを卒業して、社畜として所属するための会社(畜舎)を決めるにあたり周囲では「自社開発」>「受託開発」>「SES」の優先度で畜舎を決めた方が良いと言われました。

しかし、私はSES優先で会社を決めることにしました。

(理由が気になる方は以下ブログを参照ください。)

hira98.hatenablog.com

SESに絞った私が会社を決めるにあたり判断基準にしたのは「案件紹介所としての役割しか機能しない会社は論外。社員同士で交流できる場や社員のモチベーションを上げるための仕組み作りに力を入れているか?」だけです。

他は、給与に関しては月20あれば生活可能。勤務地に関しては案件によって変わるため、本社はどこでもいい。残業&休日出勤に関しては納期があるので許容できる。といった感じです。

約10社ほど面接を受けて、後述の理由により今の会社に転職することにしました。

  • メインで使っている開発言語がJavaSQLPythonだった。
  • 会社として今後Pythonに力を入れていくと決めて、それに応える形で社員50人がPython資格を取得した。というエピソードを聞いて、「会社が存続していくための戦略を練って、それを社員と共有する場を設ける企業風土がある」と感じたから。
  • 社員一人で案件に参画させることはなく、必ず二人以上のチームとして参画させる会社の方針。

Ansibleとの出会い

入社してからの1ヶ月は研修期間として、JavaSQL、SpringBootを学習し「Javaを極めてオブジェクト指向開発をものにするぞー」と意気込んでいました。

が...蓋をあけると「Pythonによるインフラ構築の自動化」がメインの現場に配属されることになりました。

hira98.hatenablog.com

思い描いていたのと違う😭😭

とガックリしましたが、事実の一面だけ見て凹んでいても仕方がないので、裏面を見て見ることにしました。

hira98.hatenablog.com

結果「Pythonさん🤝いやぁー😊まさかあなたとお仕事する機会が訪れるとは😲至極光栄です🥰これから末長くよろしくお願い致します🤝🙇‍♂️」と心機一転した矢先に出会ったのが、まさかのAnsibleでした。。

Ansibleと出会った場所がもう…🤮

Ansibleと違う場所で出会っていたら、こんなに嫌いになることはなかったのかもしれない。

と思えるぐらい出会った場所が最悪でした。

  • 朝早く出社すると「客」→「自社の上司」→「僕」の流れで突っ込みが入る。

    ...これに関しては割と席が近い状況にも関わらず、挨拶をしなかった自分も悪かったです💦

  • シンクラ端末というなのゴミ環境(開発に必要なソフトを入れれない、PC設定を変更できない、たまにメモリ不足で重くなる)

  • Ansibleではなく、AnsibleTowerを使っての開発。

    サンプルソースを試すのに、「ソース修正」→「コマンド実行」でできないのが辛かったです。

  • 開発メンバー分用意されていないため、使い回しているアカウント。

  • Gitを半殺しにするプロジェクト方針(ブランチ機能を使わず、GitLabのGUI機能を使ってソース修正するため1ファイル修正=1コミット)

  • 自分の作業遅延が原因で怒られ、自分の休日出勤のために多方面に根回しをするプロジェクトマネージャー。

念のため補足しておくと、プロジェクトメンバーは自分にとって優し過ぎると感じる人しかいなかったのが唯一の救いでした。

ネットワーク機器の設定の自動化

そんな劣悪な開発環境で案件に参画した私と、もう一人のメンバーは、ネットワーク機器の設定の自動化をAnsibleで行うタスクを任せられました。

このタスクを任せられた理由は、話の流れからして以下のようでした。

「とりあえず入ってもらったはいいけど、受け入れ体制が整っていないからどのタスクを任せればいいか分からない。そういえば、あのタスクは頼れるネットワークエンジニアのAさんが設計していたから、優先度は低いけどあのタスクを振っておけば後は頼れるAさんがなんとかしてくれるだろう」という流れでシステム全体の登場人物も分からないまま、タスクを振られました。

ネットワーク機器の設定自体はそんなに難しくはありません。DBから取得したデータをもとに、API叩くだけです。

しかし、これがN台になって、機器によって微妙に設定が異なる処理を「Ansible」だけで書こうとすると一気にハードモードになる。

というのが私の印象です。

今でこそ、Ansibleでも「block-when方式で分岐させたり」「辞書のリストを使って表もどきを作成して、selectattrSQLの様に扱える」ことは知っていますが、当時はそんなこと知りませんでした。

私と一緒に参画したもう一人のメンバー(女性)はAnsibleどころかプログラミングも未経験という状況でした。

前職でもプログラミングをしていた私としては、いいところを見せたい一心で、ifやfor文が使える前提でこんな感じのロジックにしたら綺麗に書けると思うので試してみます。とドヤ顔してました。

…そして1日が過ぎ、想定したロジックをAnsibleで書くことができず、赤っ恥をかく。

ということを何度か繰り返すうちに、提案することもなくなり、ネットワーク機器の台数分だけ設定するタスク(ほとんど同じ処理)を実装する。

という、DRY至上主義に反する方針を受け入れることにしました。

Ansibleに対する違和感

当時の私は、メンバー(女性)にドヤ顔したいのと、Ansibleを好きになるために、Ansibleの参考書を読んでみたり、Ansibleの勉強会に参加したりと対策は打っていました。

しかし、参考書を読んでもモヤモヤが残ります。

分岐やループの書き方は?データ構造の定義方法は?文字列操作の方法は?ファイル操作の方法は?...etc

C、JavaPythonRuby等のプログラミング言語の解説本には載っているはずの情報が殆ど載っていません。

Ansibleの勉強会に参加して、「Ansibleでこんなコード書いているんですよ」と話しても「それは、大変だねー」と同情と言葉をかけられるしまつ。

この時、以下の様なことを悶々と考えましたが、結局行動を起こして失敗するのが怖くて、流れに身をまかせることにしました。

  • 果たして、Ansibleで突き進んでいる今の開発状況はこのままでいいのだろうか?

  • Pythonを使った方がいいのでは?

  • ただ、Python使うとしてゴミ環境でどうやってPython開発環境を用意する?
  • Python開発環境構築のために誰かの手を煩わせることになる?
  • そうまでしてPythonに舵を切って、実装完了できるほどPythonを使いこなせるのか?
  • ドヤ顔提案して散々赤っ恥をかいた僕が、下手に提案してまた赤っ恥をかくぐらいなら、現状を変えずにAnsibleで突き進んだ方が楽なのでは?

…流石にこれはダメだろう

流れに身を任せて開発していた私ですが、出来上がっていくソースを読んでいて、

「流石にこれはダメだろう。。」

という思いが強くなりました。

各機器への設定情報が各ファイルに分散されており、可読性が最悪でした。。

ファイルを比較して差分をチェックしてみると、設定処理は共通で各機器への設定情報だけが差分として現れていました。

機器への設定処理は「setting.yml」でまとめて、機器ごとに異なる設定を「set_config.yml」でデータとして定義してあげることで可読性が上がり、かつ設定する機器が増えた場合へも対応できると考え、ソースを修正する方向で動くことに決めました。

修正すると決断したのはいいですが、この当時Ansibleで「block-when方式で分岐させる」実装方法をやっと知ったレベルでした。

「辞書のリストを使って表もどきを作成してselectattrSQLの様に扱える」という実装方法はまだ知りませんでした。

結果、機器一台に対して3種類の設定情報だけを定義すればいいのに、7つもの設定情報を定義するモンスターコードを書いてしまいました。

モンスタコードをおとなしくさせる実装方針に気付いた時には、時間的余裕もない状況だったため、プロジェクトマネージャーと相談してモンスターコードをなんとか飼いならす方針で進めることになりました。

今更見えてきた全体像

開発を進めていく中で見えてきた不明点を確認するために、QA表に質問を記載していき、ある程度溜まった時点で設計担当に質問していくスタイルで開発を進めていました。

ある程度質問が溜まったので設計担当に聞きに行こう。と思っていたら、設計担当が入院している事実が判明しました。

開発するにあたり不明瞭な部分があるけど突き進むしかない現状。

今まで放置プレイ状態だったのに、急に圧をかけてくるプロジェクトマネージャー。

進捗報告が苦手なので、メンバーに任せっきりの私。横で報告を聞いていて、「(爆発するかもしれない時限爆弾を抱えた状態ですが、爆弾は爆発しないと期待しているんで)進捗は順調です。」という風に聞こえてなからなかったです。

...そして、設計担当が退院したので不明点を聞いてみると、不安的中し時限爆弾が爆発しまくりました。

  • 設定対象の機器は3台だと思っていたら、実は5台だった。
  • しかも、その内1台は同じ設定をN台にする必要があった。
  • 設定前に手動でバックアップとると思っていたら、実はバックアップする処理も開発必要だった。
  • 設定追加だけだと思っていたら、実は変更するロジックも必要だった。

一覧で挙げていますが、実際には一度だけ巨大な爆発が起きたわけではなく、時間差で爆発していきました。

…空襲に怯えて生活する人の気持ちを疑似体験しているような感覚でした。

さらに、一緒に開発していたメンバーには別タスクを振られてしまい、孤軍奮闘する羽目になりました。

モンスターコードを飼いならすために

当初、3台想定だったのが5台になったことにより、以下のモンスターコードを飼いならす作業に手間取りました。

機器一台に対して3種類の設定情報だけを定義すればいいのに、7つもの設定情報を定義するモンスターコードを書いてしまいました。

私の特性として、ロジックを考えるのは好きだけど、設定表を元に機器毎のデータ定義コード化する単調な作業はとても苦手で、途中でどこまでやったか分からなくなる。ということが後で分かりました。

そんな自分の特性も分からず、モンスターコードを飼いならすためにエディタとにらめっこしてしまい無駄に時間を浪費してしまいました。

自分の特性に気付いてからは、ToDo表を作り今どこまでやったのかを一目で分かるようにして作業を進めることで、なんとかモンスターコードを飼いならして、機器の設定を入れることができました。

ヘルプで入った頼れるアニキ

自分の進捗があまりに遅れているせいで途中で入ってもらったベテランエンジニアに、

「今こんな実装になっているんで、こんな感じの方針で追加機能を実装していこうと思います」と説明をしたら、「まぁ、時間がないしそれでいいんじゃない。普段こんな設計してたら、校舎裏に呼び出すけどなw」と言われました。

怖い人だなーと思うかもしれませんが、個人的には「半月前に呼び出して欲しかったすアニキー😭😭」って感じでした。

さらに、アニキは「細かい話だけど、タスク名はギャラクシーの読み出しじゃなくてGalaxyの読み出しの方がカッコいいと思うよ」と続けました。

正直この指摘を受けた僕は感動しそうでした😭😭

今までソースも見ずに「後、どれくらいで終わるの?」と圧をかけてくる人ばかりで辛かったよー😭

「日本語」っていう、開発の進捗を正確に伝える上で、自分が一番信用していない言語を使って説明しないといけなかったから辛かったよー😭

と頼れるアニキだと思っていたんですが、

「俺は開発担当であって、君の進捗を管理するプロジェクトマネージャーじゃないから😤」

とズバッと切り捨てられるんですけどね😭😭

プライドなにそれおいしいの?

こんな状況まで追い詰められると、人間なりふり構ってられなくなります。

結果、開発エンジニアがプログラミングに対するアドバイスをネットワークエンジニアにしてもらう。という、屈辱的な状況に陥ります。

自分の使う日本語が信用されていないこと分かっているので、第3者の名前を出して説明するようになります。

設計担当の上司が、自分が所属する課のボスと何やらコソコソ話をする状況に陥ります。

「もともとxxxさんは経験者枠として入って…今更...できるわけないでしょ」

設計担当の上司が出てきて、これまで圧をかけていた設計担当が優しく接してくれる状況に陥ります。

切られた最終手段その名は「コードレビュー」

散々日本語で説明を求めてきた設計担当の上司が発した言葉。

それが「これは一度コードレビューするしかないかな。。」でした。

日本語でサンドバックになるより、プログラム言語でサンドバックになった方がマシ。

と思っている自分にとっては願ったりかなったりです。

というわけで、コードレビュー受けてサンドバックになってきまーす。

SESとして転職してからの3ヶ月を振り返って

諦めることが多すぎると人間全てがどうでもよくなってくるようです。

だからといって、自暴自棄になって流れに身を任せていると、僕みたいな状況に陥ってしまうので、皆さんは気を付けてください🤪

さいごに

SESとして2ヶ月過ごして分かったことは「SESは金で雇われた傭兵の集まり」であること。

始めて話した自社開発企業のProgateの社員像が自分の理想過ぎて、現実とのギャップに違和感しかない。

【Ansible】block-whenという書き方について

f:id:hira98:20190817155514p:plain

【結論】

  • Ansibleにif-elseはないけど、block-whenはあった。

  • block-whenという書き方が最適解かどうかはご自身で判断ください😑

  • Don't repeat yourself (DRY)信者がAnsibleと付き合うには妥協が必要。

【目次】

はじめに

現在業務でAnsibleを使って、ファイアウォール(FW)のプロビジョニング作業を自動化するための開発を行っています。

システム構成の概略は下図になります。

(FWのプロビジョニングに関係する部分だけを記載しています)

f:id:hira98:20190817155600p:plain

システムの概要をざっくりいうと、A,B,Cユーザーが各々で管理していたサーバーを一箇所で管理するためのシステムです。

新たにxxユーザーを追加する場合は次の手順で行えます。

  1. xxユーザー専用のサーバーを用意する。
  2. ユーザー情報を構成管理DBに登録する。
  3. Ansibleのプレイブックを実行すると、xxサーバーの設定を行い、FW_1とFW_2の穴あけ作業を自動で行ってくれる。

と言う一連の流れの内、私はFW_1とFW_2の穴あけ作業の自動化部分を担当しています。

FW(ファイアウォール)のプロビジョニング内容

1台のFWへのプロビジョニングは次の手順で行います。

  1. 事前確認(正常な状態か?あける予定の穴はまだあいていないか?...etc)
  2. 設定処理
  3. 事後確認(穴はきちんとあいたか?設定中に状態異常になっていないか?...etc)

今回のシステム構成では2台のFWに対してプロビジョニングを行います。

その場合の処理の流れを表したのが下図になります。

f:id:hira98:20190817155619p:plain

赤四角はFWに対して操作を行なっている部分です。FWによって必要なデータが変わってくるため、個別に渡してあげる必要があります。

また、FW_2では一部行う必要のない処理もあるため、赤四角の破線で表しています。

実装方針

FW_1とFW_2の差は渡すデータだけです。

よって、データは事前に定義しておき、事前確認、設定、事後確認は共通化する方針で実装しました。

全ソースを乗せると長くなる上に、事前確認、設定、事後確認はほぼ同じであるため、事前確認部分のソースだけを載せます。

【ファイル構成】

$ tree
├── fw_main.yml
├── tasks
    ├── fw_provisioning.yml
    └── set_config.yml

【ソース①(./fw_main.yml)】

---
- hosts: localhost  # 自分自身に接続する
  tasks:

  - name: 設定情報の定義
    include_task: set_config.yml

  - include_task: fw_ provisioning.yml
    vars:
      in_config: "{{ item }}"
    with_items:
      - "{{ fw1_config }}"
      - "{{ fw2_config }}"

【ソース②(./tasks/set_config.yml)】

---
# 構成管理DBからデータ取得
# (省略)

# FW_1とFW_2の設定情報定義
- name: 接続先情報の定義
  set_fact:
    fw1_setting:
      pre_check_1: "確認内容1"
      pre_check_2: "確認内容2"
      pre_check_3: "確認内容3"
      # 設定処理と事後チェック用の定義は省略
    fw2_setting:
      pre_check_1: "確認内容1"
      pre_check_2: "確認内容2"
      pre_check_3: false
      # 設定処理と事後チェック用の定義は省略

【ソース③(./tasks/fw_provisioning.yml)】

---
- name: 変数設定
  set_fact:
    pre_check_1: "{{ in_config.pre_check_1 }}"
    pre_check_2: "{{ in_config.pre_check_2 }}"
    pre_check_3: "{{ in_config.pre_check_3 }}"

# -------------------------------
# 事前チェック
- block:
  - name: 事前チェック1
    include_task: fw_pre_check1.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_1 }}"
  when: ( pre_check_1 != false)

- block:
  - name: 事前チェック2
    include_task: fw_pre_check2.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_2 }}"
  when: ( pre_check_2 != false)

- block:
  - name: 事前チェック3
    include_task: fw_pre_check3.yml
    vars: 
      in_data: "{{ item }}"
    with_items:
      - "{{ pre_check_3 }}"
  when: ( pre_check_3 != false)

# -------------------------------
# 設定処理と事後チェックは省略

自己満足ポイントは、FW_1とFW_2のデータは共通で定義し、FW_2で必要ない定義はfalseを設定しておき、【ソース③(./tasks/fw_provisioning.yml)】で.ymlを呼ぶかどうかをblock-whenで判定するようにしている点です。

【ソース③(./tasks/fw_provisioning.yml)】で呼んでいる事前チェック1〜3の.ymlもループを回せば記述量を減らせそうですが、そこまでやると可読性が下がる上に、Ansibleにそこまで求めると沼にはまってしまうので目を瞑っています。

補足

ちなみに、block-whenは私が勝手に言っているだけで、本来のblockは例外処理を実装するために用意されています。

  • Ansible→block-rescue-always
  • Javatry-chatch-finally
  • Pythontry-except-finally
  • Rubybegin-rescue-ensure-end

さいごに

1月程Ansibleと関わって身にしみたことは、「Don't repeat yourself (DRY)信者がAnsibleと付き合うには、

過度な期待や理想を追い求めず、信念を曲げて妥協に妥協を重ねる必要がある」ことを理解しました。