/var/log/study

つまり雑記

tentarafoo 開発 1日目

tentarafooというツール兼サーバーを書き始めた。

このツールはプロダクションで可動しているサーバーでは期待されないような状態を作り出すこと目的としている。

例えば、以下のような状態が期待されていない状態として上げられる。

  • 謎のプロセスが80/tcpを掴んでいる
  • メモリの使用量がじわじわと上がったり
  • inodeが枯渇したり
  • ディスクフルな状態
  • etc

目的を達成するだけならAnsibleとシェルスクリプトの寄せ集めのほうが早い気がするが、
シングルバイナリで様々な環境で動いてほしいのでgolangで書き始めている。

今日書いたのは以下

  • 設定ファイルを読み込む
  • プロセスタイトルを任意のものに変更できるようにした
  • tcpで複数のポートを開けるようにした
  • ddpで複数のポートを開けるようにした
  • 上記の状態で60秒待つようにした。

リポジトリは以下。

github.com

Keystoneの認証を利用してOpenStack APIを利用する

OpenStackをAPIでいろいろ操作する必要が出てきたので、OpenStack SDKに入門したのですが、微妙に躓いたのでメモを残して置きます。

2018年7月28日での話

実現したかったこと

自分が利用するOpenStack環境は、各エンドポイントがURLパスではなくドメインで分割されていたので、以下を実現したかった。

  1. keystoneに対して認証して
  2. 各エンドポイントに対してAPIをコールする

kestone認証を利用する

keystoneでsession情報を取得し、他の各クライアントにsession情報を引数で渡す。

参考にしたURLは https://docs.openstack.org/python-keystoneclient/latest/using-api-v3.html#authenticating-using-sessions

以下のような感じで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

OpenStack Docs: Python bindings to the OpenStack Nova 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ドメインある

また2018年6月16日の段階では fabricの日本語ドキュメントは2対応していないので注意

Fabric ver 2 ?

  • fabric
    • もともとあったfabric
    • python3は未対応
    • 今となってはバージョン指定をして pip インストールをする必要がある
  • fabric 2
    • 先日リリースされたやつ
    • もともとあったfabricのインターフェースを洗練させたため、fabricとは互換性がない
    • python3対応
    • pipenv install fabric で落ちてくる
  • 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
    • fab コマンドは使えるが、 invoke の機能を拡張した形
    • fabric.Connection.run を使えばよく、標準のpythonスクリプトとして実行可能
    • API的に、OOP的な感じで、直感的にわかりやすいインターフェースになった

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 がいい感じで使えるようになったからそっちで賄えそう
  • --shellenv.shell で シェルを指定できなくなった
    • fabric1のときはいろいろと指定する方法があったが面倒なので詳しくは調べていない
    • fabric2では http://www.fabfile.org/upgrading.html#run にremoteでの実行のことが書いてある
    • 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

個人的な所感としては割と良い気がする。RubyRspecの雰囲気を感じ取れる。

インストール

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 とは ?

github.com

VMware さんが作っているOSSで、コンテナをホストするためのOSです。

特徴は以下

  • おそらくRedHatベースのOS
  • tdnf (tiny dnf) という独自のパッケージ管理ツールが用いられている
  • systemd
  • network周りはsystemd-networkd

モダンなLinuxの普通構成? なのかな。

K8Sクラスタを作る方針

自分自身は過去にkubeadmでしかクラスタを作ったことが無いので、kubeadmで作りたいと思います。

kubernetes.io

で、kubeadmがサポートしているOSの中に、 Photon OS なんて無いので、 パッケージ管理ツールが絡んできそうな場所は Container Linux, それ以外のところは CentOS などを利用していくつもりです。

注意点

  • 今回はクラスタリングしたいVMがReady状態になるまでしか試していません
  • なので、クラスタが正常に動かない可能性が大いにあります
  • 個人的な不安点は、特にiptablesのルール周りです。

Photon OSの準備

github.com

上のwikiovaを使って、vSphereにデプロイ。

その後にやったことは以下

  • 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

github.com

また、PhotonOSのデフォルト状態だと、k8sAPIを叩くのに必要な6443ポートへの通信をDROPし、後の kubeadm join がfailedするのでポートを開けておく

iptables -I INPUT -p tcp --dport 6443 -m state --state NEW -j ACCEPT

一旦は検証なので、上記の感じで適当に開けるが、必要があればもう少し賢いルールを設定して、以下のissueを参考に永続化すると良い

github.com

kubeadmのインストール

基本的には以下の手順通りすすめる

kubernetes.io

上記の 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を複数台用意すること

あとは、以下に沿ってインストールを進めるだけ。

kubernetes.io

[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クラスタを作ることができました。

後日確認したいこと

kubernetes.io

上記のページに、 Check required ports があるので、そのポートが空いているか否かを確認しつつ、正常にクラスタが動いているか?を確認する必要がありそうです。

Hashicorp Nomad で タスクにクライアント側の変数を利用する

Nomad とは

Nomad というのはおしゃれな緑色のHashicorpプロダクト

www.nomadproject.io

同じく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")

ざっくりとした図は以下です。

f:id:yaaamaaaguuu:20180624120632p:plain

実現する方法

まず、クライアント側のサーバーを動かす設定ファイルの定義に 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

気づくのに時間がかかったので、覚書として。

全部ではないが、コードは以下

[Nomad] Use client side variables. · GitHub

pecoがxterm-256colorの際に文字化けるときの対処

ふとしたことがきっかけで、xterm-256colorのときにpecoが文字化けるようになってしまった。

github.com

自分と同じ症状が↑にまさしく出ていて、pecoの問題ではなく、pecoが利用しているtermbox-goとncursesとの問題とのことらしい。

自分の環境の $TERM あたりを中心に疑って見たが、pecoとncursesをアップデートしたらあっさりと治った。