@kotyのブログ

PythonとかAWSとか勉強会のこととかを、田舎者SEがつづります。記事のライセンスは"CC BY"でお願いします。

AWS Lambda+Serverless FrameworkでDjangoを使う

この記事は Django Advent Calendar 2017 - Qiita の17日目の記事です。

ここ数年サーバーレスアーキテクチャが盛り上がっているものの、Djangoと組み合わせた事例がググってもあまりないので書いてみました。ググっても出てこないということは需要が無いということかもしれませんが。。。みなさんFlaskといった軽量なwebフレームワークとDynamoって組み合わせで使ってるんですかね。

私はLambda+Djangoという組み合わせで本番投入した経験はありません。本記事は、運用に使えそうか?という観点で検証しました。

本記事のサンプルコードは以下に置いてあります。各種バージョンはgithub内のrequirements.txtを参照してください。

GitHub - koty/dj-lambda-sample: A sample of Django app on AWS Lambda using severless framework.

Djangoプロジェクトの作成

適当な名前でDjangoプロジェクトを作ります。プロジェクト構成は前述のgithubリポジトリをご確認ください。標準的なものだと思います。

Serverless Frameworkの導入

Lambdaを運用するにはこれがないとやってられません。各種リソースをzipにまとめてLambdaにdeployしてくれます。またAPI Gatewayなどの周辺リソースの設定もできます。pythonアプリを作るのにnpmモジュールを導入するのはどうなんだとも思いますけども。

www.npmjs.com

serverless コマンドを使うために -g つきでinstallします。

npm install -g serverless

VPCやIAM role等の設定が必要ですが、serverless framework自体は情報が多くありますので、ここではこれ以上触れません。

serverless-wsgi の導入

Djangoを使うにはwsgiという規格に沿う必要があります。普段EC2等で動かす場合はuwsgiやgunicornを使えばwsgiに沿うことができますが、Lambdaの場合は以下を使います。

www.npmjs.com

npm install serverless-wsgi --save-dev

serverless.ymlに以下のように追記します。

functions:
  api:
    events:  # この記述により、ルーティングをwsgi側(Django)に移譲できる。 {proxy+} の意味は不明。。documentにそう書いてある。
      - http: ANY /
      - http: ANY {proxy+}

plugins:
  - serverless-wsgi

custom:
  wsgi:  # appにはwsgi.py へのパスを記述。 serverless-python-requirementsにパッケージングを任せるため packRequirements: false を記述
    app: dj-lambda-sample.wsgi.application
    packRequirements: false

serverless-python-requirements の導入

EC2互換のdockerコンテナ上でpip install〜deployパッケージを作ってくれます。lxml等のpip install時にコンパイルをするパッケージを使っていてもEC2で問題なく動くようパッケージできます。

www.npmjs.com

npm install serverless-python-requirements --save-dev

serverless.ymlには以下のように設定します。

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true

Djangoの設定

DB の設定

使うRDBMSPostgreSQLです。MySQLよほどの理由がない限りは使わない方が良さそうです。

事前にRDSを立てておきます(EC2上に立てても当然OK)。検証が済んだら落としておくのを忘れずに。。

serverless.env.ymlを以下のように作ります。

SECURITY_GROUP_ID: 'sg-xxxxxxx'
SUBNET_ID1: 'subnet-xxxxxxx'
SUBNET_ID2: 'subnet-xxxxxxx'
DATABASE_NAME: 'xxxxx'
DATABASE_USER: 'xxxxx'
DATABASE_PASSWORD: 'xxxxxx'
DATABASE_HOST: 'xxxxxx.xxxxxxxx.ap-northeast-1.rds.amazonaws.com'
DATABASE_PORT: 5432

環境変数を作るために、serverless.ymlに以下のように設定します。外部ファイルから読み込むわけです。

provider:
  environment:
    DATABASE_NAME: ${file(./serverless.env.yml):DATABASE_NAME}
    DATABASE_USER: ${file(./serverless.env.yml):DATABASE_USER}
    DATABASE_PASSWORD: ${file(./serverless.env.yml):DATABASE_PASSWORD}
    DATABASE_HOST: ${file(./serverless.env.yml):DATABASE_HOST}
    DATABASE_PORT: ${file(./serverless.env.yml):DATABASE_PORT}

Djangoのsettings.pyに以下のように記述します。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DATABASE_NAME'),
        'USER': os.getenv('DATABASE_USER'),
        'PASSWORD': os.getenv('DATABASE_PASSWORD'),
        'HOST': os.getenv('DATABASE_HOST'),
        'PORT': os.getenv('DATABASE_PORT'),
    },
}

最後にEC2等から ./manage.py migrate します。EC2を用意するのが面倒だったので、

class MigrateView(APIView):
    def get(self, request):
        from django.core import management
        management.call_command('migrate')
        return Response({})

というviewでmigrateしました。。。現実的にはVPC内に立てたEC2からmigrateするってもんでしょう。

そのた

このへんで力尽きてきました。Lambdaでは静的ファイルはホストできないので、S3を使います。django-storageを使って、S3に向かってcollectstaticします。

まとめ

簡単なAPIであればこの構成で運用できそうです。Lambdaのスケーラビリティは魅力です。バッチ処理が入ってくるとEC2がほしくなりそうですが。。。繰り返しますが、使うRDBMSMySQLはやめた方が良いです。職場で私の斜め前の人が、どハマりしてました。

残課題

  • デプロイ時に接続が切れるか?
  • Lambdaのパッケージ容量制限はどうする?site-packagesの容量が増えるとどうなる?

もっとcoolな方法があるぜ!という方はぜひ教えてください。。

Two Scoops of Django 1.11: Best Practices for the Django Web Framework (English Edition)

Two Scoops of Django 1.11: Best Practices for the Django Web Framework (English Edition)

Vue.js 2 Cookbook: Build modern, interactive web applications with Vue.js

Vue.js 2 Cookbook: Build modern, interactive web applications with Vue.js

第一回ながのRubyの会参加記録

NSEG のコアメンバーである とみたさんが発起人となり、ながのRubyの会の第一回が開催されました。割と中信からの参加者がいらっしゃったので、第二回がもしあるなら松本か塩尻あたりで開催するのも良いんではないでしょか。

naruby.connpass.com

イベント公開後に発表者が少なめだったこともあり、とみたさんからどうよと言われたので、無理くりネタをひねり出して私も発表しました。

多少の色の違いはあるものの、両者で実現できることに大差はありません。最終的には好き嫌いの正解になるように思います。

結論を見ると割とネタに走っていますが、Ruby歴3日だとこれくらいしかできませんでした。何とぞご容赦ください。。。Rubyは今後も深入りする予定は無いのですけども、微力ながら会を盛り立てられればと思います。

個人的には、最近 google assistant アプリを作ってみているので、 Alexa skillの話が聞けたのが良かったです。(Rubyあまり関係ない)Alexaの方が開発はしやすそうに感じました。

読書会のCM

Rubyで言いますと、GEEKLAB.NAGANOでは現在隔週水曜日に「オブジェクト指向設計実践ガイド 読書会」を開催しています。次回は12月13日です。

nseg.connpass.com

オブジェクト指向の書籍はJavaを題材にしているものが多いですが、この本はRubyを題材にしているのが特徴です。ご興味ある方はぜひ参加してください。

次回のCM

NSEGの来月の勉強会は、書籍「SQLアンチパターン」の読書会スペシャルです。訳者をお招きする予定です。近々イベント立てるのでお待ち下さい。。。

第4回 オブジェクト指向設計実践ガイド 読書会 記録

nseg.connpass.com

各人それぞれの理由でキャンセルした人が何人かいたのもあり、参加者は今シーズン最少の4人でした。

スコープを制御するキーワードはrubyにはpublic/protected/privateがあるようですが、pythonにはありません。_(アンダースコア)を変数の先頭に1個つけると慣習的なprivate、2個つけると物理的なprivateという意味になります。*1

参考:9. クラス — Python 3.6.5 ドキュメント

class SomeClass(object):
    _some_protected_field = 'some_value'
    __some_private_field = 'some_value'

o = SomeClass()
print(o._some_protected_field)  # 呼べる
print(o.__some_private_field)  # ←これは実行時エラー
print(getattr(o, '__some_private_field'))  # コレもエラー
print(o._SomeClass__some_private_field)  # クラス名をつけると無理やり呼べる

最後のprivateフィールドに無理やりアクセスする方法は知らなかったかも。。この記事を書くにあたり調べました。そもそも普段はアンスコ1個のprivateフィールドしか使わないし。

デメテルの法則は普段の開発時に自然と意識しています。ピリオドが2個以上連なるとそれで良いかは一応考えます。考えてそのままにすることもよくありますが。

次回は11月です。もう年末だな。

nseg.connpass.com

*1:慣習的/物理的という表現はこの記事で私が勝手に言っているだけで本当はもっとちゃんとした用語があるのかもしれない

第3回 オブジェクト指向設計実践ガイド 読書会 記録

nseg.connpass.com

前回に引き続き11名の参加者があり盛況でした。自分はお仕事の関係で途中参加しました。

今回は依存関係に関する話題。全体としては同意ですが、パラメータの順序への依存を切るためにハッシュオブジェクトに包むというのは同意できません。訳注にあるようにRuby(またはPython)では名前付き引数を使うのが良いと思います。 こんな感じ。

class Gear(object):
    def __init__(self, chainring, cog, wheel):
      self.chainring = chainring
      self.cog = cog
      self.wheel = wheel

Gear(chainring=52, cog=11, wheel=Wheel.new(26, 1.5)).gear_inches

ハッシュに包むとIDEのコード補完の恩恵が受けられません。引数に何を指定して良いのか分からず、定義を参照せねばなりません。

ところで、参加者の中に学生さんが来ていました。聞けば文系の学部だそうですが、情報収集のためにPythonを使ってサイトのスクレイピングを試みているそうです。ぜひPython Boot Camp in 長野八ヶ岳に参加してほしいです。

pyconjp.connpass.com

彼を見ていると、ITエンジニア以外の方がやりたいことを実現するための仕掛けを自分で作る時代だなと痛切に感じます。

次回も楽しみです。

nseg.connpass.com

第2回 オブジェクト指向設計実践ガイド 読書会 記録

nseg.connpass.com

読書会常連の @tmtms さんが今回は不在でrubyを教えてくれる人がいなくて困ったな〜と思っていたら、rubyを知っている方が3人も来てくれてとっても助かりました。

外部からの参加者が5名、ギーラボ関係者が5名と最近になく賑やかな読書会になりました。全員がちょうど一度づつ読めたのも良かったです。 今回はrubyの文法についての質問が多めでしたが、rubyを知っている人にとってはrubyの文法理解に時間が多く取られるのはあまり良くないな〜と思いました。徐々に慣れるとは思うので、次回以降もrubyist諸氏におかれては今日に懲りずにご参加いただきたいです。

書籍内に出てくるコードはこちらで参照できます。 github.com

以下、普段pythonを書いている人間からの所感をいくつか。

getter/setterについて

attr_readerを使ってフィールドを隠蔽する、という話がありました。

pythonだとプロパティとフィールドのアクセス方法が同じなので、後からプロパティにもできます。

class Gear
  attr_reader :chainring, :cog
  def initialize(chainring, cog)
    @chainring = chainring
    @cog       = cog
  end

  def ratio
    chainring / cog.to_f
  end
end

は、プロパティを介さない場合は

class Gear(object):
  def __init__(self, chainring, cog):
    self.chainring = chainring
    self.cog       = cog

  def ratio(self):
    return self.chainring / self.cog

となります。これをプロパティを介すように直すと

class Gear(object):
  _chainring = None
  _cog = None

  @property
  def chainring(self):
    return self._chainring
  @chainring.setter
  def chainring(self, value):
    self._chainring = value

  @property
  def cog(self):
    return self._cog
  @cog.setter
  def cog(self, value):
    self._cog = value

  def __init__(self, chainring, cog):
    self.chainring = chainring
    self.cog       = cog

  def ratio(self):
    return self.chainring / self.cog

となる。う~ん、ちょっと記述量は多くなりますが、、、言いたいのはchainringおよびcogフィールドを使っている__init__ratioメソッドに変更が無いこと。なので後からプロパティ化することも割と手軽にできます。

配列操作について

  def diameters
    wheels.collect {|wheel| diameter(wheel)}
  end

は、pythonだと普通は内包表記で書きます。

  def diameters(self):
    return [diameter(wheel) for wheel in self.wheels]

ruby は最後に評価された値が戻り値になるそうです。これはこれで便利かも。

次回も楽しみです。

nseg.connpass.com

jinja2+WeasyPrintでお手軽印刷

過去にこんな記事を書いております。 koty.hatenablog.com この記事から4年近く過ぎた今でも印刷の需要はあります。ありますよね?

上記の記事はWPFWindowsアプリ)ですがDjangoでPDFを出す機会があったのでメモ的に残します。

おおまかな方針としては、Jinja2でHTMLを生成→WeasyPrintでPDFに変換 という流れです。

自端末のPythonのバージョンは3.6です。

requirements.txtはこちら。

WeasyPrint==0.40
Jinja2==2.9.6
Django==1.11.5

この記事としては必須ではないのですが、templateを描画するbackendがJinja2になるようsettings.pyを変更します。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
        },
    },
]

Jinja2に入力するhtmlのテンプレートはこちら。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
  <style>
    /* http://qiita.com/cognitom/items/d39d5f19054c8c8fd592 */
    @page {
      size: A4;
    }
  </style>
</head>
<body>
<h1>{{ title }}</h1>
<div>
  <ul>
    {% for skil in skills %}
      <li>{{ skil }}</li>
    {% endfor %}
  </ul>
</div>
</body>
</html>

viewはこちら。

from django.conf import settings
from django.http import HttpResponse
from jinja2 import Environment, FileSystemLoader
from weasyprint import HTML


def pdf_sample(request):
    # templateの読み込み
    template_dir = settings.TEMPLATES[0]['DIRS'][0]
    env = Environment(loader=FileSystemLoader(template_dir, encoding='utf8'))
    tpl = env.get_template('pdf_sample.html')
    # テンプレートにデータをbind
    html_str = tpl.render({
        'title':'私のスキル',
        'skills': ('python', 'django', 'C#', 'WPF', '立ち泳ぎ'),
    })
    # html文字列をPDFに変換
    pdf_file = HTML(string=html_str).write_pdf()
    return HttpResponse(pdf_file, content_type='application/pdf')

あら簡単ですね!実際にはcssでいろんな調整が必要だろうと思います。この辺を参考に。 qiita.com

PythonでPDF印刷について調べるとReportLABとかがヒットします。これだとAPIがプリミティブ過ぎて使いづらいように思います。線を一本一本引いていくなんて辛すぎます。何らかの雛形を用意してそこにデータを突っ込む、ということをしたいわけです。それにはjinja2が最適です。

1ページだけの単票(死語か?)であればこの仕掛けで十分なように思います。 一方、改ページつきで各ページに共通なヘッダを出すといった帳票の場合は何か他の、例えばWPFとかを使う必要があるかもしれません。HTMLでもできるんかな??

本件のサンプルプロジェクトは以下です。 github.com

参考サイト:

PythonユーザのためのJupyter[実践]入門

PythonユーザのためのJupyter[実践]入門

PyCon JP 2017 に参加してきた

参加された皆さま、スタッフの皆さま、お疲れさまでした。簡単ですが参加報告です。

ブースを出した

昨年からスポンサーをさせていただいていましたが、今年はブースも出しました。その辺の様子は以下をご覧ください。

katekichi.hatenablog.com

私が転職してきてから数えても3回目の参加となり、だいぶ知り合いも増えてきました。そういった方々と立ち話しするだけでも楽しくすごせました。

セッションの所感

聴講セッション一覧

1日目

2日目

今年は英語のセッションを多めに聴講してみたのですが、正直言ってほとんど分かりませんでした。。。pyconに来るたびに英語力の無さを痛感します。*1その他に印象に残ったセッションについて所感を書きます。

プレゼンテーション:Industrial Test Automation with Asyncio | PyCon JP 2017 in TOKYO

  • async awaitは実用できるっぽい。すくなくとも発表者の用途では
  • pytestっていうテストのためのパッケージがあるらしい。unittestよりもイケてるらしい
  • まとめでこんなようなことを言っていたと思う(英語なので自信なし
    • プログラマは内にこもりがち。外に出ていきコミュニケーションをとろう
    • blogを書こう
    • OSSに貢献しよう

プレゼンテーション:Building a Customized Personal Assistant with Python | PyCon JP 2017 in TOKYO

フロントエンドにfacebook messenger、バックエンドに ChatterBotなるフレームワークを使ったという事例。面白そうだけど日本語の辞書がないっぽい。。

プレゼンテーション:ドローンのフライトコントローラをPythonで制御してみた話 | PyCon JP 2017 in TOKYO

大学の研究発表みたいだった。モーター動かせたって所までだったけど実際には制御系を作る必要があってその辺りの調整が難しかろうと思う。フライトコントローラーをOSSで作るという取り組みは素晴らしいです。

プレゼンテーション:The theory of Serverless development by Python (理論から学ぶPythonによるサーバレス開発) | PyCon JP 2017 in TOKYO

AWS Lambdaの話。質疑応答の時間が取られなかったので発表者に直接聞いた。

補足をいただいた。

ということで参考になりました。 発表の中で「Web APIアプリケーションには別に向いてない」というような説明がありましたが、それだけそういった使われ方が多いんでは、ということを帰社後にお客さんと話していました。


確かにこれは思った。

来年は少なくともプロポーザルは出したいなぁ。

*1:ちなみに翌日TOEICを受検している