技術をかじる猫

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

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 のインストールとサービス登録までします。
続きを読む

AmazonLinux2 に ansible 2.7 で JDK11 をぶち込む

qiita.com

VM 作ったらまずは署名認証

VM を立ち上げて、 ssh を確認(今回は 192.168.56.2 にした)したら、署名で SSH できるようにする。
とりあえず mac 側で署名署名などを作る

$ cd ~/.ssh
$ ssh-keygen -t rsa -b 4096 -C "hoge@example.com" -f ~/.ssh/virtualbox
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/xxxx/.ssh/virtualbox.
Your public key has been saved in /Users/xxxx/.ssh/virtualbox.pub.
The key fingerprint is:
SHA256:eKnjs4jiHsEktydZ7McMGULVDBQ/Wg3kHpeewVfGVmc hoge@example.com
The key's randomart image is:
+---[RSA 4096]----+
| .oo**o    .o.. E|
|   o =o+ . oo  o |
|... = * * ..     |
|+. = B * =       |
| o+ + * S        |
|  .o . o         |
| .    o          |
|. .. o..         |
|o+. . oo         |
+----[SHA256]-----+
$ ls 
known_hosts   virtualbox  virtualbox.pub

したら公開鍵を転送

$ scp virtualbox.pub ec2-user@192.168.56.2:~/
ec2-user@192.168.56.2's password: 
virtualbox.pub 
$ ssh ec2-user@192.168.56.2                            

authorized_keys に登録する

$ ssh ec2-user@192.168.56.2 
$ cd .ssh/
[ec2-user@amazonlinux .ssh]$ ls -l
合計 0
-rw------- 1 ec2-user ec2-user 0  319 21:22 authorized_keys
[ec2-user@amazonlinux .ssh]$ cat ~/virtualbox.pub >> authorized_keys 

ansible 設定

適当に ansible.cfg を作る。
曰く、ローカルディレクトリにおいとけば効くのだそうだ

[defaults]
host_key_checking=False
inventory=/Users/xxxx/workspace/VirtualBox/hosts
private_key_file=/Users/xxxx/workspace/VirtualBox/virtualbox

なんか勢いでディレクトリ作っちゃったので、署名の類も移動するか

mv ~/.ssh/virtualbox* /Users/xxxx/workspace/VirtualBox/

hosts も作って

[develop-server]
192.168.56.2

これでもうコマンドは通るはずなので

$ ansible all -u ec2-user -m ping
Enter passphrase for key '/Users/xxxx/workspace/VirtualBox/virtualbox': 
192.168.56.2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

OK通ったね。

Amazon Corretto11 を DL させてインストールする

といっても、単純に Playbook を書くだけ。

- hosts:  develop-server
  user:   ec2-user
  sudo:   yes
  vars:
    corretto: https://d3pxv6yz143wms.cloudfront.net/11.0.2.9.3/java-11-amazon-corretto-devel-11.0.2.9-3.x86_64.rpm
    java_home: /usr/lib/jvm/java-11-amazon-corretto
  tasks:
    - name: Update all package
      yum:
        name: '*'
        state: latest
    - name: Install wget.
      yum:
        name: wget
        state: latest
    - name: Install corret.
      yum:
        name: '{{ corretto }}'
        state: present
    - name: Alternatives.
      alternatives:
        name: '{{ item }}'
        link: "/usr/bin/{{ item }}"
        path: "{{ java_home }}/bin/{{ item }}"
      with_items:
        - java
        - javac

実行すると

$ ansible-playbook java11.yml 
[DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in version 2.9. Deprecation 
warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

PLAY [develop-server] ******************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************
Enter passphrase for key '/Users/xxxx/workspace/VirtualBox/virtualbox': 
ok: [192.168.56.2]

TASK [Update all package] **************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Install wget.] *******************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Install corret.] *****************************************************************************************************************************************************************
ok: [192.168.56.2]

TASK [Alternatives.] *******************************************************************************************************************************************************************
ok: [192.168.56.2] => (item=java)
ok: [192.168.56.2] => (item=javac)

PLAY RECAP *****************************************************************************************************************************************************************************
192.168.56.2               : ok=5    changed=0    unreachable=0    failed=0   

問題なく終了し、

$ ssh ec2-user@192.168.56.2
ec2-user@192.168.56.2's password: 
Last login: Tue Mar 19 23:55:37 2019 from 192.168.56.1

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@amazonlinux ~]$ java --version
openjdk 11.0.2 2019-01-15 LTS
OpenJDK Runtime Environment Corretto-11.0.2.9.3 (build 11.0.2+9-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.2.9.3 (build 11.0.2+9-LTS, mixed mode)

完了。
調べながらでも 2h で済むか…