技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

アプリからドライバまで 現役エンジニアの濃い話

sciencepark.connpass.com

gitその2 rebaseとrebase -iを理解してgit-flowをやりやすくする :サイエンスパーク

author: 須藤さん

www.slideshare.net

前回、gitsvn の違いから始まって、git-flow のモデルまで説明されました。
今回は git-flow の流れの続き。
rebase + rebase -i についての話です。

ざっくりまとめると以下の機能があります。

  1. rebase 指定したバージョンを元にコミット差分を乗せ直す機能です。
    コミット履歴を見れば非常にわかりやすいですが、ブランチマージと異なっていて、コミットや修正の履歴が直列になります。
  2. rebase -i はバージョンを指定して、履歴を操作する…という意味合いまでは一緒ですが、こちらはコミット履歴の統廃合することができます。
    これによって、任意のタイミングでコミットしても後から整形すればいいという使い方ができます。

rebase -i (コミット範囲) をすると、コミットログの一覧が出る。
これを編集して、git rebase continue すると、コミットログが綺麗にまとまる。

なぜこれらが必要なのかというと、github でプルリクエストをする時に、マージだと更新履歴がまともに見えないので論外。rebase は必須としても、履歴が汚れている場合は rebase -i をした方がいいというお話です。

内容的には初級向けの印象でした。
rebase -i 久々に見ました…自分一人でやってるとコミット履歴気にしないもんなぁ(汗

Scikit-learnで機械学習系の何か

author: 安原

機械学習とAI の言葉の範囲が異なるというところから始まり、機械学習にも様々な種類があるというお話から始まりました。
そこから、決定木と、ランダムフォレストの説明につながりました。

  • 決定木: 与えられた情報を「XX が YY 以上なら…」という様にしてデータを判断するツリーを決定木と言います。
  • ランダムフォレスト: 決定木を学習データをランダム振り分けで学習させ、しかもそれを複数本用意するアルゴリズム
    こうした複数の学習機を利用して多数決で決定する様なアルゴリズムをアンサンブル学習と言います。

こちらも機械学習初級向けの内容とは思います。
決定木が学習する過程で条件を学習選択するアルゴリズムなどに関して突っ込んだら 絶対深かった 多分収集つかなくなった(汗

マインドフルネス

www.slideshare.net

今この瞬間に意識を傾けて物を考えるという方法。
集中力アップのほか、ストレス軽減の効果がある。

マインドフルネス瞑想!

  1. 力を抜いて楽にする
  2. 目を閉じます
  3. 鼻から深呼吸します
  4. 呼吸を数回繰り返します
  5. キリがいいくらい(5分位)その状態を維持します

変に何か感じたら、呼吸に意識を傾けることで頭をリセットします。
これを知ってると気分のリセットなどには実に有用です。

唯一のライフハック系のお話!
これ系は勉強会で出てくることは滅多にないので貴重です(^_^;)

Kubernetesの勉強がてら勉強会

参加してきたのは以下。

SpringBootでマイクロサービスを作ってKubernetesにデプロイしてみる【実践編】 dreamarts.connpass.com

尚、当日資料は下記。

github.com

K8S とは?

コンテナオーケストレーション技術。
コンテナオーケストレーションは、

  • コンテナが落ちた時に自動でコンテナを用意してアップしてくれる
  • 新しいバージョンリリースの時には「新しいコンテナを作って新版デプロイ」「接続設定を新版に切り替え」「旧版コンテナの削除」を順次行ってくれる

要するにバックのサービス云々を意識せすに、サービス維持を自動化してもらうツールと言える。

特徴として

  • Declarative 宣言的
  • Immutable 固定化
  • AutoHealing 自己修復的

この特徴のおかげで、「致命的エラーが出た?新しいコンテナ作ればいいやん」という状況が生まれる。
ただし、このアーキテクチャを採用する場合

  • サーバサイドにセッション情報を保持する作りは崩壊する。
    • つまりステートレスサーバを作らなければならない(破棄前提)

個人的な意見だと Playframework でやればおk!

K8s の構成

基本的に

  • kubectl
    コントロールサーバ。ここから K8s
  • kube-apiserver
    kubectl からリクエストを受ける部分。
    こいつが kube-scheduler に処理を移譲する
  • kube-scheduler
    apiserver で受けた内容を元に、Pod を操作する部分。
  • Kubelet
    Podを起動し管理するエージェント
  • Pod
    コンテナ本体

という構成とのこと。

Docker

カーネルレベルの機能としてプロセスを分離し、そこで Ubuntuなどの様なコマンドセットを用意することで、それぞれのディストリビューションとして振る舞う。
という様なお話。

すみません、自分で調べてたので聞き流してしまいました(汗

Docker 周りはこの辺聞けばいいと思う。

11. dockerネットワーキングとか、kubernetesネットワーキングとか | Fukabori.fm

いまさらDockerに入門したので分かりやすくまとめます - Qiita

Flask 1.0.2 入門してみた

見通しよく、REST API を書きたいと思ったのだけど、Flask-restful がなんともしっかりした REST 特化しすぎてて微妙だったので、Flask 単品でちょっと実験したメモ

Install といっても…

Python だとこれだけ。

$ pip install Flask

ルーティング機構

と言っても非常にみたまま素直な感じ。
とてもわかりやすくてよい。

from flask import Flask, request

app = Flask(__name__)

@app.route('/seminers', methods=['GET', 'POST'])
def seminers():
    print(request)
    print(request.data)
    print(request.form)
    print(request.args)
    return 'Example message'

app.run(debug = True, port = 3000)

リクエスト等の受け取り

で、どうなるのか気になったのがここなので、色々上記に流し込んで試してみた。

まずは単純リクエス

$  curl http://localhost:3000/seminers

実行結果…まぁそうね。
null になってないあたり個人的に好き

<Request 'http://localhost:3000/seminers' [GET]>
b''
ImmutableMultiDict([])
ImmutableMultiDict([])

URL パラメータ突っ込むと

$ curl http://localhost:3000/seminers?welcome=data

args が反応する

<Request 'http://localhost:3000/seminers?welcome=data' [GET]>
b''
ImmutableMultiDict([])
ImmutableMultiDict([('welcome', 'data')])

今度は Form POST

$ curl -F "key=value" -F "msg=ひゃっはー"  http://localhost:3000/seminers

request.form が反応

<Request 'http://localhost:3000/seminers' [POST]>
b''
ImmutableMultiDict([('key', 'value'), ('msg', 'ひゃっはー')])
ImmutableMultiDict([])

ファイルを送信してみる。適当に ss.png ファイルをキャプチャで作って食わせた

$ curl -F "key=value" -F "file=@ss.png" http://localhost:3000/seminers

えーっと(汗

<Request 'http://localhost:3000/seminers' [POST]>
b''
ImmutableMultiDict([('key', 'value')])
ImmutableMultiDict([])

調べてみたら files フィールドに格納されるらしい

@app.route('/seminers', methods=['GET', 'POST'])
def seminers():
    print(request)
    print(request.data)
    print(request.files)
    print(request.form)
    print(request.args)
    return 'Example message'

書き換えたリトライ。
でたでた。form/maltipart-formdata の区別は自動でやってくれてるらしい。

<Request 'http://localhost:3000/seminers' [POST]>
b''
ImmutableMultiDict([('file', <FileStorage: 'ss.png' ('application/octet-stream')>)])
ImmutableMultiDict([('key', 'value')])
ImmutableMultiDict([])

なら、JSON はどうだろう?

$ curl -H 'Content-Type:application/json' -d "{"key":"val","key2":",val2"}" http://localhost:3000/seminers

ここにきて data が反応。
application/json にはデフォルトでは対応していない模様。

<Request 'http://localhost:3000/seminers' [POST]>
b'{key:val,key2:,val2}'
ImmutableMultiDict([])
ImmutableMultiDict([])
ImmutableMultiDict([])

json パッケージとか使えばどうとでもなりそう。

import json

@app.route('/seminers', methods=['GET', 'POST'])
def seminers():
    print(request)
    print(request.data)
    print(request.files)
    print(request.form)
    print(request.args)
    if request.headers['Content-Type'] == 'application/json':
        json_value = json.loads(request.data)
        print(json_value)
    return 'Example message'

そして投げ直す

$ curl -H 'Content-Type:application/json' -d "{\"key\":\"val\",\"key2\":\"val2\"}" http://localhost:3000/seminers

うまく行ったけど、あまりにダサい。
if とか腹立たしいのだけど…

<Request 'http://localhost:3000/seminers' [POST]>
b'{"key":"val","key2":"val2"}'
ImmutableMultiDict([])
ImmutableMultiDict([])
ImmutableMultiDict([])
{'key': 'val', 'key2': 'val2'}

application/json にしか公開しないとかねーのかと思ったのだけど、

flask.pocoo.org

まぁこういう振り分けこそコントローラの仕事だし仕方ないのか(汗

Pandas の練習がてら、勉強会のトレンドを探ってみた

この辺の続きです。

white-azalea.hatenablog.jp

4月の勉強会の開催状況と、事前申し込みの状況を眺めてみたが正解。

勉強会情報を拾ってくる

前回の流れですが、2サイト以上でやるので、少しだけ汎用化を考えてみた。
調べてみたら、ATND と COMPASSAPI仕様がほぼ同一だった。

まずは共通の定義を用意して

import requests
import sys

class ClassRoom:
    def __init__(self, title, limit, accepted, waiting):
        def or_zero(v):
            if isinstance(v, int):
                return v
            return 0
        self.title = title
        self.limit = or_zero(limit)
        self.joins = or_zero(accepted) + or_zero(waiting)

    def __str__(self):
        return f'ClassRoom({self.title}, {self.limit}, {self.joins})'


class ClassRoomLoader:
    def load_class_rooms(self, month: str) -> list:
        pass


class AbstractCompassAtnd(ClassRoomLoader):
    def __init__(self, api_url: str, max_event_num: int):
        self.api_url = api_url
        self.max_event_num = max_event_num


    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "format": "json", "start": top}


    def request_to_compass(self, target_month: str, top: int) -> requests.Response:
        params = self.create_request_parameter(target_month, top)
        query = '&'.join([f'{key}={params[key]}' for key in params.keys()])
        print(f'Request: {self.api_url}?{query}')
        return requests.get(self.api_url + '?' + query)


    def convert(self, event) -> ClassRoom:
        try:
            return ClassRoom(event['title'], event['limit'], event['accepted'], event['waiting'])
        except:
            print(f'Error: {sys.exc_info()[0]}')
            print(f'Value: {event}')
            raise Exception('Cannot convert')


    def load_request(self, target_month: str, top: int):
        class_rooms = []
        r_get = self.request_to_compass(target_month, top)
        print(r_get.status_code)

        respond_json = r_get.json()

        for ev in respond_json['events']:
            class_rooms.append(self.convert(ev))

        return (respond_json['results_returned'], class_rooms)


    def load_class_rooms(self, month: str) -> list:
        getted = self.max_event_num
        current_top = 1
        class_rooms = []
        request = 0

        while getted == self.max_event_num:  # 最大数以下が返ってきている == まだ次がある
            request += 1

            getted, tmp_rooms = self.load_request(month, current_top)
            current_top += self.max_event_num
            class_rooms.extend(tmp_rooms)

            print(
                f'Request: {request}, Return: {getted}, currentTop: {current_top}')

        return class_rooms

単純に読み込む。
尚、読み取った勉強会情報は、pickle でダンプしておく。

class AtndLoader(AbstractCompassAtnd):
    COMPASS_API = 'http://api.atnd.org/events/'
    WANT_EVENT_NUM = 100

    def __init__(self):
        super().__init__(AtndLoader.COMPASS_API, AtndLoader.WANT_EVENT_NUM)

    def convert(self, event) -> ClassRoom:
        return super().convert(event['event'])

    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "format": "json", "start": top}



class CompassLoader(AbstractCompassAtnd):
    COMPASS_API = 'https://connpass.com/api/v1/event/'
    WANT_EVENT_NUM = 100

    def __init__(self):
        super().__init__(CompassLoader.COMPASS_API, CompassLoader.WANT_EVENT_NUM)

    def create_request_parameter(self, target_month: str, top: int):
        return {"ym": target_month, "count": self.max_event_num, "order": 1, "start": top}

# import pickle for backup dump.
import pickle
if __name__ == "__main__":
    class_rooms = AtndLoader().load_class_rooms("201904")
    class_rooms.extend(CompassLoader().load_class_rooms("201904"))
    with open('backup.pickle', 'wb') as f:
        pickle.dump(class_rooms, f)
    print("\n".join([str(s) for s in class_rooms]))
続きを読む

SQLAlchemy のさわりだけ

SQLAlchemy?

Python の O/R マッパーです。
スクリプト言語だし、たかが数行試すだけならSQL 文書いても…とは思ったのだけど、せっかくなので

検証は

  • python : 3.6.8 (anaconda)
  • sqlite3 インストール済み
  • SQLAlchemy : 1.3

インストール

$ pip install SQLAlchemy

スキーマ作成

まずはインストールして

# Importing SQLAlchemy
from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.exc import NoResultFound

どうもテーブルがなければ作るという挙動らしい。

# Create Schema.
Base = declarative_base()

class ClassRoomInfo(Base):
    __tablename__ = 'class_room_info'
    id = Column(Integer, primary_key = True, autoincrement = True)
    title = Column(String(512))
    limit = Column(Integer)
    joins = Column(Integer)

    def __repr__(self):
        return "<ClassRoomInfo(id='%s', title='%s', limit='%s', joins='%s')>" % (self.id, self.title, self.limit, self.joins)

Base.metadata.create_all(engine)

セッションを作って操作

コネクションの類は全部セッションがどうにかしてくれるご様子。

# Database session start.
Session = sessionmaker(bind=engine)
session = Session()

# ここに何かやりたいこと

# End of session.
session.close()

INSERT / SELECT

さすが Python 見たままやなー

# Isertion to database.
session.add(ClassRoomInfo(title='ひゃっはー', limit=20, joins=10))
# 中略(500 件くらい挿入)

# commit
session.commit()

# Selection
id50 = session.query(ClassRoomInfo).filter_by(id = 50).one()
print(f'ID 50 = {id50}')

Anaconda パッケージのアップデート

といっても、そんな大したコマンドではないので、あくまでメモ

$ sudo conda update --all

Python 3.6.8 までが今日時点で出てたのでサクッと入れた。
問題は pandas とかアップデート入ってたから、前に書いたサンプルとか動かなくなってそうな点…

結構 Depricate あったからなぁ(汗

AmazonLinux2 に Java11 + Tomcat9 を Ansible2.7 で突っ込む

やろうと思ったきっかけは、そんなわりかし新しい構成でサーバ作ってと頼まれたので。

そして、やってみるとわかるのだけど、2019/03/21 時点でこれをリポジトリサポートしてるLinuxがそうそう無いと言うことに気づく。
強いて言えば Ubuntu18 が JDK11 だけサポートしてた。

でも Tomcat の Java11 対応って、Tomcat9 からなので、それなりに最新を入れざるを得なかったという問題があった。

前回もそんな流れで、JDK11 だけ先行して入ってりゃどうにかなんだろーと思ったためだ。

white-azalea.hatenablog.jp

しかし甘かった…Tomcat9 の問題が待ち構えていたのだ。

なので、今回は前回をパワーアップチャレンジした。

インストールの段階を分離

Playbook は本来やりたいことの単位で区切っておいた方が、可読性の問題から言ってもいいので、全体として 4 つの playbook を作った。

  • main.yml
    メインの playbook。他の platbook を呼び出すハブ
    • common.yml
      yum update だけしておく。
    • java.yml
      内部で Java バージョンを指定し、既存のインストールバージョン指定
    • tomcat.yml
      Tomcat のインストールとサービス登録までします。
続きを読む