Memo Log

PANTO MAIMU 's Personal Blog

今日届いた本

色々身辺整理をしていて、なかなかブログの更新ができていません(´・ω・`)
そんな中、頼んでいた本が届きました :-)

Kafka

f:id:PANTOMAIMU:20180805122539j:plain
Kafka

Kafka

Kafka

オライリーから出た、Kafkaの翻訳本です。
最近はk8sの仕事をしたりしていますが、Kafkaもやっておかないとなぁ...と思って、買ってみました。
お盆休みは、積読している本を少しは消化しないと...orz

今日届いた本

今週はたて続けw

対話型組織開発

f:id:PANTOMAIMU:20180705210125j:plain
対話型組織開発
対話型組織開発――その理論的系譜と実践

対話型組織開発――その理論的系譜と実践

タイトルと目次に釣られて買ってみた...
コプリエンの組織パターンと、井庭研のコラボレーションパターンとを組み合わせて読むと面白いかも...と思っている

組織パターン

組織パターン (Object Oriented SELECTION)

組織パターン (Object Oriented SELECTION)

今日届いた本

そんなわけで、色々身辺整理真っ最中だったりしますヾ(:3ノシヾ)ノシ

そんな中買った本

アニメスタイル013

f:id:PANTOMAIMU:20180703202150j:plain
アニメスタイル013
アニメスタイル013 (メディアパルムック)

アニメスタイル013 (メディアパルムック)

さよならの朝に約束の花をかざろう』の特集ということで購入...
円盤の発売が10/26に決まって、予約注文済みヾ(:3ノシヾ)ノシ

入門 Kubernetes

f:id:PANTOMAIMU:20180703202205j:plain
入門 Kubernetes
入門 Kubernetes

入門 Kubernetes

近々、お仕事でk8s関係のことをやることになりそうなので購入...
元々、ここまでコンテナに関わるつもりはなかったのだが....
真面目に勉強しよう...orz

今日届いた本

そんなわけで、今日も本が届きましたヾ(:3ノシヾ)ノシ

Elasticsearch実践ガイド

f:id:PANTOMAIMU:20180619194004j:plain
Elasticsearch実践ガイド
Elasticsearch実践ガイド (impress top gear)

Elasticsearch実践ガイド (impress top gear)

一部の人たちから、日本語で書かれているまとまった資料としていいよ~という話があったので買ってみました...
まぁ、仕事でElasticsearchに思いっきり関わるケースは少ないのですが、知っておいた方がよさそうなので...

今日届いた本

ちょっとプライベートがバタバタしていて、ブログ更新ができていません(´・ω・`)

そんな中、本を注文。

インフラCI実践ガイド

f:id:PANTOMAIMU:20180617184719j:plain
インフラCI実践ガイド
インフラCI実践ガイド Ansible/GitLabを使ったインフラ改善サイクルの実現

インフラCI実践ガイド Ansible/GitLabを使ったインフラ改善サイクルの実現

最近、AnsibleやJupyter Notebookを使った運用をする機会が増えてきているので、この辺のことに力を入れなきゃ...ってことで購入 :-)

Hadoop クラスター構築実践ガイド

f:id:PANTOMAIMU:20180617184740j:plain
Hadoop クラスター構築実践ガイド
ビッグデータ分析基盤の構築事例集 Hadoop クラスター構築実践ガイド (impress top gear)

ビッグデータ分析基盤の構築事例集 Hadoop クラスター構築実践ガイド (impress top gear)

Hadoopに直接かかわる機会はなかったのですが、この辺の知識は他でも応用が利くので読んどかないとなぁ~と思って購入。
この本は、データ分析に焦点を絞った内容だということで買ってみました。

Ansible Dynamic Inventoryスクリプトのサンプルを作ってみた

Ansibleの話題というと、大体がplaybookの話になってしまっていて、構成管理情報(インベントリファイル)についてはあまり突っ込んだ話がされることがないなぁ...と思うことがあります。

※データではなくロジックばかり語られている感じ...

大規模なシステムの場合、Ansibleが標準で提供しているインベントリファイルの仕様では、一つのファイルにまとめるにしても、複数のファイルに分けるにしても、可読性が悪くなるため構成管理情報のメンテナンスが大変になり、人為的ミスが発生しやすくなる問題があります。

f:id:PANTOMAIMU:20180506165941p:plain
Ansibleの標準的な構成管理

この問題に対する解決策としてよくあるのが、構成管理情報DB(RDBExcel上)で構成管理情報をメンテナンスし、この構成管理情報DBからインベントリファイルに変換して運用する方法です。

f:id:PANTOMAIMU:20180506165935p:plain
構成管理情報DBからインベントリファイルに変換して運用
既存の構成管理情報DBの流用もでき、RDBExcelの機能を使ってバリデーションを行うこともできるため、規模の大きなシステムではこの方法を使った事例が多いと思います。

ただこの方法だと、構成管理情報DBとインベントリファイルの2か所に構成管理情報が存在するため、データが二重管理される状態になる問題があります。
運用手順を守っていれば大きな問題にならないのですが、緊急対応が必要になった場合、大抵手順は守られません...orz

f:id:PANTOMAIMU:20180506165931p:plain
構成管理情報の二重管理問題
このため、構成管理情報DBとインベントリファイルの間で設定値が不一致状態となり、構成管理情報DBからインベントリファイルを変換した際に、緊急対応で設定した値が古い値で上書きされるという問題が起きます。
まぁ、データを二重管理した際によく起きる問題です....orz

この構成管理情報を二重管理しなくて済むようにするため、Ansibleから構成管理情報DBに直接アクセスできれば、二重管理問題を解決することができます。
これに、AnsibleのDynamic Inventory機能が使えればいいなぁ...というのがあって、今回Dynamic Inventoryスクリプトのサンプルを作ってみました。

f:id:PANTOMAIMU:20180506165926p:plain
Dynamic Inventoryスクリプト

Dynamic Inventoryというのは、Ansibleが解釈できるJSON形式の構成情報を返すスクリプトを利用する方法です。
上の図では、Dynamic Inventoryスクリプトが構成管理情報DBを参照して、JSON形式の構成情報をAnsibleに返しています。

Dynamic Inventoryですが、EC2やOpenStack用のものがすでに用意されています。
要は、自分たちのシステム用のDynamic Inventoryスクリプトを作るためのサンプルを作ってみました...という感じですヾ(:3ノシヾ)ノシ

Excelファイルを読み込むDynamic Inventoryスクリプトの説明

それで、今回作成したDynamic Inventoryスクリプトのサンプルは、Excelファイルを読み込んでJSON形式の構成情報を返すものです。
Excelファイルを構成管理情報DBと見立てたものだと思って下さい。

スクリプトExcelファイルは、以下のGitHubに保存しています。
pullするなりダウンロードするなりして使ってみてください。
github.com

まずは構成管理情報DBに当たるExcelファイルについて説明します。

Excelファイルの説明(inventory.xlsx)

このExcelファイルには、Ansibleでアクセスする先のホスト情報を定義するhostsシートと、保守対象のサービスごとのシートがあります。

hostsシート

このhostsシートでは、AnsibleでsshでアクセスするIPアドレスssh接続ユーザ名称を設定します。

f:id:PANTOMAIMU:20180506174733p:plain
hostsシート
groupには、ホストの属するgroupを定義します。このgroupは、Ansible実行時に指定するgroupに使うことができます。
最初にこのhostsシートに、運用対象hostを追加してきます。
ansible_userには、ssh接続ユーザ名称を設定します。未定義の場合は、ansible.cfgで設定されたユーザ名称が使用されます。

サービスごとのシート

次にサービスごとのシートの説明です。
このシートは、保守対象のサービスごとに作成してください。hostsという名称のシートでなければ問題ありません。
サンプルでは、WebサービスとHA Proxyを想定したものを設定しています。

f:id:PANTOMAIMU:20180506174727p:plain
Webサービス用シート
f:id:PANTOMAIMU:20180506174722p:plain
HA Proxy用シート

このサービスごとのシートのA、B列にあるgroupとhost_nameの定義は必須になります。
C列以降は、サービスごとに拡張してください。
1行目にあるタイトル行に記述したタイトル名称が、Ansible側でインベントリ情報を参照する際の項目名称となります。

host_nameには、hostsシートに記述しているホスト名称(host_name)を指定して紐づけます。
複数のサービスシートから、一つのホストを指定しても問題ありません。
例えば、1つのホストにWenサービスとRDBサービスを混在させることはよくあるので、それを想定しています。

groupには、そのサービス内でグループを分けたい場合に使用します。
ただこのgroupは必須なので、すべてを同一グループにしたい場合は、同じグループ名称を設定してください。
なお、このシート名称をグループ名としたグループも、自動的に生成されます。

JSON形式の構成情報出力例

まずは、Dynamic Inventoryスクリプトを実行して、JSON形式の構成情報を出力してみます。
この際、Dynamic Inventoryスクリプトに実行権限を付与してください。
また、このスクリプトが利用しているyamlパッケージと、Excelファイルにアクセスするためのopenpyxlパッケージを事前にインストールしておいてください。

$ sudo pip3 install openpyxl
$ sudo pip3 install pyyaml

それから、excel_inventory.pyの先頭に記述されているPythonコマンドへのパスの記述は、各自の環境に合わせてください。
※このスクリプトはPython3向けに書いているので、各自のPython環境に合わせてください。

それでは、以下のようにDynamic Inventoryスクリプトを実行してみてください。

$ ./excel_inventory.py  | python -m json.tool

出力として、以下のようなJSON形式の構成情報が出力されます。
このデータをみれば、大体何ができるのかが見えてくると思います。

{
    "_meta": {
        "hostvars": {
            "haproxy001": {
                "ansible_host": "192.168.0.5",
                "frontend_ip": "10.100.0.1",
                "frontend_port": 80
            },
            "haproxy011": {
                "ansible_host": "192.168.0.14",
                "frontend_ip": "10.100.1.1",
                "frontend_port": 80
            },
            "webserver001": {
                "ansible_host": "192.168.0.1",
                "is_backup": false,
                "web_ip": "10.0.0.1",
                "web_port": 8080,
                "weight": 1
            },
            "webserver002": {
                "ansible_host": "192.168.0.2",
                "is_backup": false,
                "web_ip": "10.0.0.2",
                "weight": 2
            },
            "webserver003": {
                "ansible_host": "192.168.0.3",
                "is_backup": false,
                "web_ip": "10.0.0.3",
                "weight": 3
            },
            "webserver004": {
                "ansible_host": "192.168.0.4",
                "is_backup": true,
                "web_ip": "10.0.0.4"
            },
            "webserver011": {
                "ansible_host": "192.168.0.11",
                "is_backup": false,
                "web_ip": "10.0.1.1",
                "weight": 1
            },
            "webserver012": {
                "ansible_host": "192.168.0.12",
                "is_backup": false,
                "web_ip": "10.0.1.2",
                "weight": 3
            },
            "webserver013": {
                "ansible_host": "192.168.0.13",
                "is_backup": false,
                "web_ip": "10.0.1.3",
                "weight": 5
            }
        }
    },
    "all": {
        "vars": {
            "all_test1": 123234,
            "all_test2": 2.13,
            "all_test3": true,
            "all_test4": "test_data"
        }
    },
    "cluster001": {
        "hosts": [
            "webserver001",
            "webserver002",
            "webserver003",
            "webserver004",
            "haproxy001"
        ]
    },
    "cluster002": {
        "hosts": [
            "webserver011",
            "webserver012",
            "webserver013",
            "haproxy011"
        ]
    },
    "haproxy": {
        "hosts": [
            "haproxy001",
            "haproxy011"
        ],
        "vars": {
            "frontend_port": 80,
            "ha_proxy_conf_path": "/etc/haproxy/haproxy.cfg"
        }
    },
    "web001": {
        "hosts": [
            "webserver001",
            "webserver002",
            "webserver003",
            "webserver004",
            "haproxy001"
        ]
    },
    "web002": {
        "hosts": [
            "webserver011",
            "webserver012",
            "webserver013",
            "haproxy011"
        ]
    },
    "webservers": {
        "hosts": [
            "webserver001",
            "webserver002",
            "webserver003",
            "webserver004",
            "webserver011",
            "webserver012",
            "webserver013"
        ],
        "vars": {
            "web_conf_path": "/etc/httpd/conf/httpd.conf",
            "web_port": 80
        }
    }
}

この構成情報が、Ansibleで利用できることを確認してみます。

$ ansible all -m debug -a "msg={{ ansible_host }}"
haproxy011 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.14"
}
haproxy001 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.5"
}
webserver001 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.1"
}
webserver002 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.2"
}
webserver003 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.3"
}
webserver004 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.4"
}
webserver012 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.12"
}
webserver011 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.11"
}
webserver013 | SUCCESS => {
    "changed": false,
    "msg": "192.168.0.13"
}

groupごとに属するホストが定義されているので、allの部分をExcelシートに指定したgroup名にすれば、そのグループのhostのみになります。
playbookのhostsに、サービスごとのシート名称を指定すれば、誤って異なるサービスのノードに対して処理が実行されることもなくなります。

次に、共通情報定義ファイルについて説明します。

共通情報定義ファイル(common_val.yml)

この共通情報定義ファイルでは、参照するExcelファイル名称と、groupごとの共通定義を指定しています。
通常のインベントリファイルのgroupvars相当のものです。
specific_varsというのは、今回のスクリプトではまだ使用していませんが、スクリプト内でAnsibleに渡す値を生成するためのカスタマイズ処理関数に引き渡す値を指定します。
このあたりについては、実際にexcel_inventory.pyを読んでみてください。

# inventory file list
inventory_file: inventory.xlsx

# all group vars
all_vars:
  all_test1: 123234
  all_test2: 2.13
  all_test3: True
  all_test4: test_data

# group vars
group_vars:
  haproxy:
    ha_proxy_conf_path: /etc/haproxy/haproxy.cfg
    frontend_port: 80

  webservers:
    web_conf_path: /etc/httpd/conf/httpd.conf
    web_port: 80

# specific values
specific_vars:
  specific_data: specific_val

ansible.cfg

Ansibleではお約束のものです。
inventoryに、今回のスクリプトを設定します。

[defaults]
remote_user = root
inventory = excel_inventory.py
host_key_checking = False

ここで指定したスクリプトには、実行権限が必要なので、chmodで実行権限をつけておいてください。

ざっくりとですが、AnsibleのDynamic Inventoryスクリプトのサンプルについて説明しました。
このサンプルでは、Excelファイルを構成管理情報DBとしていますが、Excelの代わりに構成管理情報が管理されているRDBを参照するようにするといった機能拡張をすることも可能です。
AnsibleのEC2やOpenStack用のDynamic Inventoryスクリプトでは、AWSやOpenStackのAPIを呼び出して、似たようなことをしています。

また、今回のサンプルではExcelファイルの値をそのまま構成情報の項目として設定していますが、このスクリプト内で項目値を生成して登録することもできます。
実装はしてませんが、HA Proxyの項目にバランシング先のホストや重みづけを設定したリストを生成して渡すことが可能です。
ノードの増減によって設定値が変わるようなロードバランサへの設定を、転送先ノードの情報から生成できるようにすれば、冗長な設定も不要になると考えています。

それでは~

Pythonのopenpyxlを使ってExcelファイルを読んでみる

久しぶりの技術ネタ...です

今、AnsibleインベントリファイルとしてExcelファイルを使えるようにするための、ダイナミックインベントリスクリプトを作っていたりします。
このダイナミックインベントリスクリプトPythonで作っているのですが、ここでExcelファイルにアクセスするためのライブラリであるopenpyxlを使っています。
openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files — openpyxl 2.5.0 documentation

今回は、このopenpyxlを使って、Excelファイルから値を読み込む方法について簡単に書いてみます。
※このopenpyxl、Excelファイルの生成から変更など色々できるのですが、今回は参照機能しか使ってないので...

openpyxlのインストール

まずは、pipを使ってopenpyxlをインストールします。
今回、Python3.6を使っているので、3.6のpipを使っています。

$ sudo pip3.6 install openpyxl

私が3.6環境で開発しているだけなので、pip3.6を使っていますが、各自の環境にあったpipを使ってください。

Excelファイルを読みこんでみる

それでは、実際にExcelファイルを読み込んでみます。
今回読み込んでみるExcelファイルの内容は、以下の通り...

f:id:PANTOMAIMU:20180504205858p:plain
今回読み込むサンプルExcelファイル

文字列、数値、日付、Boolean、未入力セルを含んだものにしています。

Excelファイルの読み込み処理

まずは、Excelファイルの読み込みと、Book内のシート名一覧の取得コードです。

import openpyxl as px

# Excelファイルを読み込み、Bookオブジェクトを生成
workbook = px.load_workbook('sample.xlsx', data_only=True)

load_workbook関数でBookを読み込みます。
戻り値はBookオブジェクトになります。

data_only=Trueはデータ値のみを取得するという設定で、セルに記述している数式は読み込まなくなります。
data_only=Falseにすると、セルに記述された数式(SUM(B2:B4) など)がそのまま出力されます。
このため、openpyxlを使って値を読み込ませる際は、数式を計算結果に置き換えてから読み込ませる必要があります。

次は、Bookオブジェクトのsheetnamesから、Bookに含まれるSheetの名称一覧を取得しています。

# book内のsheet名称一覧を取得する
sheet_names = workbook.sheetnames
# sheet名称一覧 表示
print (sheet_names)

このsheetnamesで取得したSheet名称一覧の内容は、以下のようになります。

['sample', 'Sheet2', 'Sheet3']

Bookに含まれるSheetを取得する際には、Sheet名を指定して取得するのですが、ハードコーディングでSheet名称を指定するより、この方法で取得したSheet名称一覧を使う方が柔軟性が上がると思います :-)

Sheetの読み込みとCell情報の取得

次は、BookオブジェクトからSheetを取得します。

# sheetの読み込み
sheet = workbook[sheet_names[0]]

Bookオブジェクトを辞書に見立てて、[シート名称]と記述することで、目的のSheetオブジェクトを取得することができます。
ここでは、前に取得したSheet名称一覧の先頭にあるSheet名称を使って取得しています。

次は、上記で取得したSheetオブジェクトから、イテレーションを使ってCellを取得するコードです。

# sheet内のcell情報の取得
for row in sheet:
    for cell in row:
        # cellのデータ取得
        # cellの行番号取得(1オリジン)
        row_pos = cell.row
        # cellの列番号取得(1オリジン)
        column_pos = cell.col_idx
        # Excel表記のcellの列番号(A,B...)
        column = cell.column
        # cellのデータ型
        cell_type = cell.data_type
        # cellのデータ
        value = cell.value

        print ('row_pos=%d column_pos=%d column=%s cell_type=%s value=%s' %
                 (row_pos, column_pos, column, cell_type, value))

Pythonではお約束の、for xx in yy を使って、Sheetオブジェクトからrowを取得し、そのrowオブジェクトからイテレーションでCellオブジェクトを取得します。
取得したCellオブジェクトから、Cellの位置や、Cellに含まれているデータの型やデータそのものを取得しています。
取得したデータをprintで出力した結果は、以下の通り。

row_pos=1 column_pos=1 column=A cell_type=s value=text
row_pos=1 column_pos=2 column=B cell_type=s value=整数
row_pos=1 column_pos=3 column=C cell_type=s value=実数
row_pos=1 column_pos=4 column=D cell_type=s value=datetime
row_pos=1 column_pos=5 column=E cell_type=s value=boolean
row_pos=2 column_pos=1 column=A cell_type=s value=data001
row_pos=2 column_pos=2 column=B cell_type=n value=10
row_pos=2 column_pos=3 column=C cell_type=n value=10.52
row_pos=2 column_pos=4 column=D cell_type=d value=2018-05-02 00:00:00
row_pos=2 column_pos=5 column=E cell_type=b value=True
row_pos=3 column_pos=1 column=A cell_type=s value=data002
row_pos=3 column_pos=2 column=B cell_type=n value=21
row_pos=3 column_pos=3 column=C cell_type=n value=29.39
row_pos=3 column_pos=4 column=D cell_type=d value=2018-10-05 10:00:00
row_pos=3 column_pos=5 column=E cell_type=b value=False
row_pos=4 column_pos=1 column=A cell_type=s value=testdata
row_pos=4 column_pos=2 column=B cell_type=n value=None
row_pos=4 column_pos=3 column=C cell_type=n value=None
row_pos=4 column_pos=4 column=D cell_type=n value=None
row_pos=4 column_pos=5 column=E cell_type=n value=None

取得したデータは、Excelのセルの型に対応する、Pythonのデータ型の値として取得することができています。
実際のCell内でのデータ型は、cell.data_typeで取得することができます。
文字列は’s’、booleanは'b'、日時型は'd'という文字が返ってきます。
数値とデータなしでは’n’が返ってきます。ちょっとこれはわかりにくいなぁ...
このCellの型の内容や変数についての詳細は、以下のドキュメントに詳しく書かれています。
openpyxl.cell.cell module — openpyxl 2.5.0 documentation

そんな訳で、openpyxlのファイル参照について簡単に説明してみました。
運用とかで、ネットワーク機器やサーバの構成情報をExcelファイルで管理しているケースってよくあるので、そのExcelファイルから、運用で使うスクリプトが使いやすい文字形式(JSONYAML)に変換する際に重宝するのではないかと思っています。
VBAで記述する方法もあるのですが、使い慣れたPythonスクリプトの方が便利なので...orz

それでは