Keystoneの認証を利用してOpenStack APIを利用する
OpenStackをAPIでいろいろ操作する必要が出てきたので、OpenStack SDKに入門したのですが、微妙に躓いたのでメモを残して置きます。
2018年7月28日での話
実現したかったこと
自分が利用するOpenStack環境は、各エンドポイントがURLパスではなくドメインで分割されていたので、以下を実現したかった。
- keystoneに対して認証して
- 各エンドポイントに対してAPIをコールする
kestone認証を利用する
keystoneでsession情報を取得し、他の各クライアントにsession情報を引数で渡す。
以下のような感じでSDKを利用しておけば良い気がする。
pre requirement
pip install python-novaclient python-glanceclient python-keystoneclient
コード
import os from keystoneauth1.identity import v3 from keystoneauth1 import session from glanceclient import Client from novaclient import client as novaclient def get_keystone_credential(): return { 'auth_url': os.getenv('OS_AUTH_URL', ''), 'user_id': os.getenv('OS_USER_ID', ''), 'password': os.getenv('OS_PASSWORD', ''), 'project_id': os.getenv('OS_PROJECT_ID', ''), } if __name__ == '__main__': auth = v3.Password(**get_keystone_credential()) sess = session.Session(auth=auth) glance = Client('2', session=sess) print("---images---") for image in glance.images.list(): print(image['name']) print("---servers---") nova = novaclient.Client('2', session=sess) for s in nova.servers.list(): print(s.name)
エンドポイントを指定する
こちらに関しては、利用したい各クライアントによって指定方法が異なる気がする。
glanceなら Clientを生成するときの引数で endpoint
を指定するし、
https://github.com/openstack/python-glanceclient/blob/master/glanceclient/client.py#L23
novaだとhttps://github.com/openstack/python-novaclient/blob/master/novaclient/client.py#L270 から生成されるであろう https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/client.py#L50 には endpoint_override
という引数で指定できそう
ココらへんのことを書いてあるドキュメントを見つけられず、コードを読んで結論を出したので、もしかしたらもっと良い方法があるかもしれない。
反省点
軽い気持ちでSDKというくくりの日本語ドキュメントを読み始めたのが間違いだった。
各クライアントのドキュメントを読むと良い気がする。
OpenStack Docs: Python bindings to the OpenStack Identity API (Keystone)
OpenStack Docs: Python Bindings for the OpenStack Images API
python fabric2 の話
先日、おもむろに pipenv install fabric
したら、fabricのver 2が落ちてきた。
今までのfabricのインターフェースとは大きく変わっていたのでざっくりとまとめておく。
TL;DL
- fabric2は良い
- oopなインターフェースを備えた
- 1ライブラリというスタンスが明確になった
- fabric1のユーザーは http://docs.fabfile.org/en/2.1/upgrading.html に目を通しましょう。
- デグレっぽいときはだいたい機能を削った理由が書いてある
Fabric
- 自動化のためのツール
- ShellScriptでは微妙だけど、Ansibleを持ち出すほどでもないときによく使われる印象
公式ドキュメント
どのような意図なのか?はわからないが、公式ドキュメントは2ドメインある
- ざっくりした概要が書いてある場所
- APIのドキュメント
また2018年6月16日の段階では fabricの日本語ドキュメントは2対応していないので注意
Fabric ver 2 ?
- fabric
- もともとあったfabric
- python3は未対応
- 今となってはバージョン指定をして pip インストールをする必要がある
- fabric 2
- fabric 3
- fabricが長い間 python3 に未対応だったためのfork
- fabric の ver 1のインターフェースと ほぼ 互換性がある
- コードを読むとわかるけど、一部非互換のところもあったよ
- python3対応
pipenv install fabric3
で落ちてくる
ver 1 と ver 2 のgetting start 的な比較
すごく雑だが、パスワード認証でログインできるサーバーにrootで hello world
を出力するサンプル
ver 1
from fabric.api import run, env env.hosts = ["192.168.0.1"] env.user = "root" def hello(): run("echo hello world")
fabfile.pyのあるディレクトリで fab hello
で実行可能
ver 2 を ver 1っぽい書き方で
from invoke import task @task def hello(c): c.run("echo hello world")
fabfile.pyのあるディレクトリで fab --host root@192.168.0.1 --prompt-for-login-password hello
ver 2 を ver 2っぽい書き方で
from fabric import Connection c = Connection(host="192.168.0.1", user="root", connect_kwargs={"password": "xxxxxxx"}) c.run("echo hello world")
何が変わったのか?
- ver 1
fab
コマンドがメイン- fabfile.py で、関数の中に
fabric.api.run
を書いて、fab 関数名
- 改めて考えると、マジックな感じが多く、fabricに対する知識が求められる感じ
- ver 2
fabricの1と2では、機能的側面で互換性があるので、fabric 1の利用者は http://docs.fabfile.org/en/2.1/upgrading.html を読むことをおすすめする。自分が気になった非互換は以下
fabric ver 1 と ver 2での非互換
http://docs.fabfile.org/en/2.1/upgrading.html からの抜粋して雑に意訳とその感想
- role がなくなった
- Group がいい感じで使えるようになったからそっちで賄えそう
--shell
やenv.shell
で シェルを指定できなくなった- fabric1のときはいろいろと指定する方法があったが面倒なので詳しくは調べていない
- fabric2では http://www.fabfile.org/upgrading.html#run にremoteでの実行のことが書いてある
- google先生に翻訳してももらうと
- 指定できるのは嬉しいけど、コマンドのエスケープがバグりがち?
- runの引数にshellはあるけど、他のインターフェース(invoke.runners.Runner)のサブクラスだからあるだけで何もしないとのこと
- たぶんココらへんにそのコードがある https://github.com/fabric/fabric/blob/2.0/fabric/runners.py#L18
- google先生に翻訳してももらうと
- remoteに対しては runメソッドの引数などを利用せず、commandで明示的に指定するようにしてほしい感じだと思う
- ディレクトリごとファイルを送れなくなった
- http://www.fabfile.org/upgrading.html#file-transfer の一番下に書いてある
- rsync + zip or tar みたいなコードをメンテするのが大変だったからやめたとのこと
- 自分はローカルでzipにして、リモートに送ってunzipするようにした
雑な感想
fabric ver 2では直感的にわかりやすいし、個人的には良いと思えるインターフェース設計になった印象があるので、積極的にfabric1から移行したい。
mambaを利用してpythonでBDD
pythonで利用できるBDDのツールに良いのが無いよなぁと思っていたら、きちんと awesome-python に記載されていた。
Mamba — mamba 0.9.2 documentation
個人的な所感としては割と良い気がする。RubyのRspecの雰囲気を感じ取れる。
インストール
pipenv install mamba
書き方
with
句と mamba
で用意されている description
もしくは describe
, context
, it
と、 expects
というパッケージに用意されている expect
などを利用して普通のBDDがpythonでも書ける感じ。
_context
などのアンダースコアで始まるのはペンディングの印
全く意味がないテストコードだけど、以下の様。
from mamba import describe, context, it, _context, _it from expects import expect, be_true, equal from faker import Faker # サンプルのデータを作るやつ fake = Faker() fake.name() with describe("New topi") as self: with context("Bool"): with it("True"): expect(True).to(be_true) with describe("Pendins") as self: with _context("penndins"): with it("pending"): pass with describe("Pendins") as self: with context("penndins"): with _it("pending"): pass class Human(object): def __init__(self, name): self._name = name @property def name(self): return self._name with describe("Human") as self: with before.each: self.human = Human(fake.name()) with it("has name"): expect(type(self.human.name)).to(equal(str))
ライフサイクル
ライフサイクルとしては以下のようになっている
- before.all
- (describe内の) テストを実行する前に1度だけ
- before.each
- (describe内の) 各テストを実行する前に毎回
- after.each
- (describe内の) 各テストを実行したあとに毎回
- after.all
- (describe内の) テストを実行した後に1度だけ
参考は以下
Hooks — mamba 0.9.2 documentation
実行方法
pipenv run mamba **/*_spec.py
とかで実行可能
また pipenv run mamba --enable-coverage
と実行した後に、 pipenv run coverage html
でカバレッジを見ることができる。
VMware Photon OS で構築するK8Sクラスタ
Photon OS とは ?
VMware さんが作っているOSSで、コンテナをホストするためのOSです。
特徴は以下
- おそらくRedHatベースのOS
- tdnf (tiny dnf) という独自のパッケージ管理ツールが用いられている
- systemd
- network周りはsystemd-networkd
モダンなLinuxの普通構成? なのかな。
K8Sクラスタを作る方針
自分自身は過去にkubeadmでしかクラスタを作ったことが無いので、kubeadmで作りたいと思います。
で、kubeadmがサポートしているOSの中に、 Photon OS
なんて無いので、 パッケージ管理ツールが絡んできそうな場所は Container Linux
, それ以外のところは CentOS
などを利用していくつもりです。
注意点
Photon OSの準備
その後にやったことは以下
hostnamectl set-hostname name
で適当にhostnameを変更- 100GBのディスクを
/var/lib/docker
に追加 - echo 'export PATH="${PATH}:/opt/bin"' >> ~/.bash_profile
tdnf install -y tar wget ebtables ethtool socat
K8S向けの特別な対応
何もせずに、kubeadmでセッティングしていくと、 kube-dns
というやつがクラッシュして再起動しまくるので、issueを参考に設定を入れる
cat << EOF > /etc/docker/daemon.json { "exec-opts": ["native.cgroupdriver=cgroupfs"] } EOF
また、PhotonOSのデフォルト状態だと、k8sのAPIを叩くのに必要な6443ポートへの通信をDROPし、後の kubeadm join
がfailedするのでポートを開けておく
iptables -I INPUT -p tcp --dport 6443 -m state --state NEW -j ACCEPT
一旦は検証なので、上記の感じで適当に開けるが、必要があればもう少し賢いルールを設定して、以下のissueを参考に永続化すると良い
kubeadmのインストール
基本的には以下の手順通りすすめる
上記の Installing kubeadm, kubelet and kubectl
の項目は Container Linux
の全部と CentOS
の以下のコマンドを実行した
cat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sysctl --system
kubernetes のインストール
ここまでの状態のPhotonOSのVMを複数台用意すること
あとは、以下に沿ってインストールを進めるだけ。
[WARNING FileExisting-crictl]: crictl not found in system path
というWARNINGが出るが、 2018/06/24 時点では、crictlをインストールするとk8sのインストール段階でエラーになって結局ignore-errorするハメになるので無視で良さそう
自分は3VM用意して kubectl get nodes
の結果は以下のような状態になった。
root@k8s-photon-001-v [ ~ ]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-photon-001-v Ready master 34m v1.10.5 k8s-photon-002-v Ready <none> 31m v1.10.5 k8s-photon-003-v Ready <none> 31m v1.10.5
検証中はちょいちょいハマりどころはあったものの、特に問題なくPhotonOSとkubeadmでK8Sクラスタを作ることができました。
後日確認したいこと
上記のページに、 Check required ports
があるので、そのポートが空いているか否かを確認しつつ、正常にクラスタが動いているか?を確認する必要がありそうです。
Hashicorp Nomad で タスクにクライアント側の変数を利用する
Nomad とは
Nomad というのはおしゃれな緑色のHashicorpプロダクト
同じくHashicorpのConsulというツールと連動してジョブのスケジューラーとして動作してくれる製品です。
アーキテクチャ
NomadにはServerというユーザーからジョブを受け付けるヤツと、Clientというジョブを実際に実行するヤツとがいるっぽいです。
あまりきちんとドキュメントを読み込んでいないので、正しくないかもしれないですが。
実現したいしたいこと
あるジョブを実行した際、ホストごとに設定される環境変数を読み取り、挙動を変えたいと思いました。
以下のようなコードの中の DOMAIN
をクライアントごとに切り替えたいという話です。
from flask import Flask import os app = Flask(__name__) @app.route("/", methods=["GET"]) def root(): return os.getenv("DOMAIN", "None") if __name__ == "__main__": app.run(host="0.0.0.0")
ざっくりとした図は以下です。
実現する方法
まず、クライアント側のサーバーを動かす設定ファイルの定義に meta
というセクションを指定し、その中で適当に変数を宣言します
client { enabled = true servers = [ "ipaddress" ] meta { domain="This is domain value." } }
次に、ジョブの定義の中で、クライアントのmetaを拾えるようにします。
以下の設定の中の、 envというところで、 DOMAIN="${meta.domain}"
を指定しています。
こうすることで、上で示した実行したいコードは DOMAIN
という環境変数がセットされた上で実行されるようになります。
job "web-app"{ datacenters = ["dc1"] type = "system" update { stagger = "10s" max_parallel = 1 } task "webservice" { driver = "docker" config { image = "flask-app:1.2.0" port_map { web = 5000 } } resources { network { port "web" {} } } env { DOMAIN = "${meta.domain}" } } }
以下の話は、ドキュメント に以下のように記載されていました。
${meta.key} | Metadata value given by key on the client | ${meta.foo} => bar
気づくのに時間がかかったので、覚書として。
全部ではないが、コードは以下
pecoがxterm-256colorの際に文字化けるときの対処
ふとしたことがきっかけで、xterm-256colorのときにpecoが文字化けるようになってしまった。
自分と同じ症状が↑にまさしく出ていて、pecoの問題ではなく、pecoが利用しているtermbox-goとncursesとの問題とのことらしい。
自分の環境の $TERM
あたりを中心に疑って見たが、pecoとncursesをアップデートしたらあっさりと治った。