AWS Lambda 上での Python API サービスをローカルでも検証するために serverless-wsgi を利用する

serverless framework を使って Web API を作りたいと思っていて色々調べていたのだけれど、以下のような場合にどうしようかなあと思っていた。

  • AWS S3 Bucket上に展開したWeb Hosting上からAPI Gateway経由でサービスを叩くアーキテクチャ
  • つまり別途フロントエンドのウェブページがあり、ブラウザを使ってAPI連携の検証をちゃんとしたい

もちろんステージの概念があるのでAWSにフルデプロイしながら検証することもできなくはない。
が、開発環境のテストにおいてローカルで完結できるというのは個人的な信条から必要だと思っているので何とかローカルで完結する検証環境をちゃんと作ってしまいたい。

TL;DR

  • serverless framework だと serverless-offline という plugin があるんだけれど現時点ではPythonバージョンだとちょっと使えなさそう
  • WSGI のレイヤーを挟むことでローカルで検証する場合のアプリケーションサーバーをローンチして検証できる
  • serverless framework の plugin として serverless-wsgi が提供されているのでこれを使う
  • localでの検証は sls wsgi serve で、本番へのデプロイは sls deploy でそれぞれ対応可能になる
続きを読む

WSL on Dockerでbridgeネットワークを使うと通信速度がやけに劣化する

Windows Subsystem for Linux に apt 経由で docker をインストール*1したものの、どうにもこうにも処理が遅い。 元々のきっかけは docker をビルドに利用する別のツールを利用していたのだけれど、1ビルドに数十分かかるのでいったいなぜだと調べ始めたこと。

結論から言うと、根本原因までは調べ切れてないですが暫定解決策はあるので、1ケースとして書き留めておきます。

TL;DR

  • WSL on Docker を使っている場合に Docker コンテナ内部からの通信がものすごく遅い事象に遭遇した
  • docker run なら --net=host, docker build なら --network host を使うことで暫定解決はできる

*1:Docker for Windowsではなく、apt-get install docker.io でインストール

続きを読む

serverless frameworkでpublic repositoryに含めるべきではないデプロイ情報を扱う

AWS Lambdaの開発環境としてServerless Frameworkを使ってみることにしました。

Frameworkと銘打っていますがソフトウェアの中に組み込んで使うフレームワークではなく、各種のSource Code as a Serviceに対するデプロイや運用をサポートしてくれるためのツール*1になります。

で、チュートリアルにならって serverless.yml の設定を書いていたのですが、以下の疑問が沸いてきました。

  • provider.deploymentBucket.name でデプロイ先バケットを指定できるが、publicリポジトリに登録する場合にうれしくないのでは?*2
  • 同様に各種ソースコードで利用する極秘情報(API Keyなど)をどのように保持するのか。

で、調べてみた結果、Systems Manager Parameter Store を利用するか、依存関係を別ファイルに切り出すかで解決できます。

Systems Manager Parameter Store を利用する

Parametere Store へのアクセスができないとかでない限りは基本的にこちらを推奨します。

https://dev.classmethod.jp/cloud/aws/parameter-store-application-config/

アプリケーションで利用する環境変数を EC2 Systems Manager で利用可能にする機能ですが、これを serverless framework のデプロイおよび実行の際にも利用することが可能です。

まず、デプロイ実行時に利用する変数をawscliでデプロイします。

# 必要に応じて --profile を指定する
aws ssm put-parameter --name sls.deploymentBucket --type String --value your-bucket-name

その後 serverless.yml 内で ${ssm:} と記述することでそこに登録している値を利用できます。

# serverless.yml から抜粋
provider:
  deploymentBucket:
    name: ${ssm:sls.deploymentBucket}

API Tokenなどの秘匿情報を保持させる場合、aws ssm put-parameter --type SecureString を利用して暗号化した情報をParameter Storeに保存します。

しかし、SecureStringで暗号化されたパラメータを serverless.yml 内で指定した場合は暗号化されたままの内容が serverless.yml 上に展開されます。
SecureStringで指定した内容はデプロイ時には利用せず、Lambdaの内部で Systems Manager の GetParameters を利用して複合してください。

例えばPythonの場合は boto3.client('ssm').get_parameter(Name=key, WithDecryption=True) などで獲得できます。

より細かくは公式のチュートリアルを参照ください。

https://serverless.com/blog/serverless-secrets-api-keys/

依存関係を別ファイルに切り出す

基本的には Parameter Store を利用することを推奨しますが、例えばAWSアカウントのSSMアクセス権限がない場合などに利用できます。

serverless.yml の中で別のファイルを読み込み、その変数を使うことができます。
その時には ${file(ファイル名):内部変数名} を利用できます。

# serverless.yml から抜粋
provider:
  deploymentBucket:
    name: ${file(./secrets.yml):SLS_DEPLOY_BUCKET}
# secrets.yml の一例
SLS_DEPLOY_BUCKET: your-bucket-name

Systems Manager Parameter Store について

というかサービスの詳細を見落としてましたが、コレものっっすごく便利ですね。

割とAWSデプロイ時の変数をうまいこと格納できる方法がリポジトリに同梱するとかS3にアップロードしておくとかの方法しか思いつかなかったので、ここの値をうまく使うのはいい感じにデプロイ時の値の保持を解消してくれるソリューションとして利用できて非常にうれしいです。

なお、通常の ssm get-parameter / put-parameter を利用する場合は AWS が SSM を利用するときにデフォルトで生成する alias/aws/ssm という KMS 管理下の鍵を内部で利用して自動的に暗号化・複合化してくれているようなので、自前で暗号化・複合化に利用する鍵のことを考えなくて済むのも非常にポイントが高いです。

*1:最初は名前だけ聞いてどうやって各種のプロジェクトに組み込むものかと思っていた

*2:バケット名は全AWSアカウントでユニークである必要があり、自分以外の人がチェックアウト・デプロイするタイミングでそのまま使おうとすると該当AWSアカウントのクレデンシャルを持っていない限り絶対に失敗する

ファイル操作をmock化する

open とか codecs.open でファイルを開く場合は基本 with句で使っているので単純にopenをモックに書き換えようとすると相当面倒くさい。そのために mock_open という関数が用意されている。

open() をコンテキストマネージャーとして使う方法は、ファイルが必ず適切に閉じられるようにする素晴らしい方法で、今では一般的になっています

問題は、 open() をモックアウトしたところで、コンテキストマネージャーが使われる (__enter__() と __exit__() が呼ばれる) のはその 戻り値 だということです。
コンテキストマネージャーを MagicMock でモックするのは一般的かつ面倒なので、ヘルパー関数を用意しています。

https://docs.python.jp/3/library/unittest.mock.html#mock-open

具体的な使い方は以下の通り。 Read/Writeともに同じ open 関数を使っている限りは両方の呼び出しがmockされる。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import codecs
from unittest.mock import MagicMock, patch, mock_open   # python 2 の場合は from mock

from nose.tools import eq_, ok_

def readwrite():
    with codecs.open('read.txt', 'r') as fh:
        reads = fh.readlines()
    with codecs.open('write.txt', 'w') as fh:
        fh.write('line1\n')
        fh.write('line2\n')
        fh.write('line3\n')    
    return reads

def test_mock_open():
    mockio = mock_open(read_data='readline1\nreadline2\nreadline3\n')
    with patch('codecs.open', mockio):
        reads = readwrite()
        
    # 1. read mocked data
    eq_(reads, ['readline1\n', 'readline2\n', 'readline3\n'])
    
    # 2. how to check write contents
    fh = mockio()  # Get file handler mock object by calling mockio object
    actuals = [args[0] for (args, kwargs) in fh.write.call_args_list]
    eq_('line1\n', actuals[0])
    eq_('line2\n', actuals[1])
    eq_('line3\n', actuals[2])


if __name__ == '__main__':
    test_mock_open()

Github上のPythonプロジェクトに必要なテストツールとCIツールのセットアップを行った話

Githubに公開するなら、普通のPython関連の周辺ツールのインストールや設定をしてみようと、色々と試行錯誤したところでハマったところとかポイントとか工夫とかのメモ。

今回はプロジェクトの構造は boto3 のものをまねることにした。

対象プロジェクト - https://github.com/a-hisame/testplay-card-generator

続きを読む

nosetestsをWindows Subsystem for Linuxで使う場合の注意点

Pythonのunittest実行ライブラリの nose ですが、ファイル名に test がついているテストファイルを nosetests コマンドで自動検索しようとしても全然引っかからない事象に遭遇。 一応、直接ファイルを指定すれば実行はしてくれる。

で、色々なところを見て回ったんですが、最も肝心な注意書きの否定形を見落としていました。

It is important to note that the default behavior of nose is to not include tests from files which are executable.

http://nose.readthedocs.io/en/latest/usage.html より引用

今回は Windows のドライブをマウントしたところにファイルをチェックアウトしていたので、Linux側でのパーミッションは777固定で変更できません。
そのため、実行権限あり状態のファイルしか存在しないので、デフォルトの挙動だとスキップされてしまうんですね。

解決策は上記のページの中にも書いてある通り --exe オプションを付けて実行するだけ。 これで解決。

なお、うまく読み込めない場合は以下の通りの方法で verbosity 3 (-vv) を付けてどのようなサーチができたかを調べると楽。

# これなら実行できる
nosetests --exe 

# なにかうまく読み込めないなーと思った場合
nosetests -vv --collect-only

ボードゲームカード自作補助のためのツール testplay-card-generator のα版を公開しました

別の名義を使ってはいますが、個人的な趣味でボードゲームの自作などをやっています。
そういったゲームを作る中でも汎用性が高く、印刷しやすいコンポーネントにカードがあります。

で、実際にルールを考えてテストプレイをするタイミングで実際に考えた内容を印刷・裁断して、それをスリーブに入れてテストプレイを行うのですが、正直このカードのデータを作った後に印刷するのに結構苦労していました。

やり方としては Excel のラベル印刷機能を使ったこともあったのですが、あの機能だとレイアウトの保存できないので印刷の都度レイアウトの編集をしなくてはいけないのが相当手間でした。
その他色々と探してみたのですが、どうにも自分でピンと来るものがなかったので、いっそもう自分で作ろうと思って作りました *1

https://github.com/a-hisame/testplay-card-generator

このツールは以下のような機能を備えています。

  • コンポーネントの描画: 四角、楕円、線などをレイアウト上で定義することでカード上での簡易版レイアウトを印刷物に表現できる
  • テキスト描画のレイアウトとフォントサイズ自動調整: 文字を描画するときに指定された領域からはみ出している場合に自動改行・自動サイズ調整などを行ってくれる
  • レイアウトテンプレートとデータソースの分離: レイアウトファイル(YAML)によって描画コンポーネントとその描画順序を定義し、データソース(CSV)によって実際のカードのデータを定義する。 Excelとかで作成したカードリストをCSV保存して流し込むことを想定
  • 印刷に適したファイルの出力: 作成したカードを印刷用のPDF、あるいはPNGファイルとして保存できる

直近で自分に必要な最低限の機能だけを詰め込んであるので、現時点のバージョンはαです。
具体的にはバリデーションが相当あまかったり、PILの仕様をうまくできていなかったり、処理効率に若干の問題があったりします。
ただ、現時点でも最低限のテストプレイ用カードの印刷は実施できるはずだと思います。


そして、需要がそこそこあるのならばAPI経由でテストプレイ用の画像生成ができるサービスを公開したい気はあるんですが、実際のところどのぐらい需要があるのだろう。
というか、世の中の同人デザイナーさんはテストプレイのカードをどうやって作っているのだろう。

*1:ここ数日のPDFおよびPIL関連の調査はこれをやりたかったがためです