nykergoto’s blog

機械学習とpythonをメインに

実践Django / 一歩先に進むことが出来る本

このたび実践Django Python による本格Webアプリケーション開発を著者の芝田さんから頂きました。もともとこの本は買おうと思っていたので、大変嬉しくちょうだいいたしました。芝田さん、ありがとうございます:D

頂いてから1ヶ月弱ほどたってしまったのですが、この本を読んで思った良い所、どういう人に読んで欲しいかなど、僕なりの感想を書いていければなと思っています。

www.shoeisha.co.jp

本当にざっくりとですが、本書の特徴的な部分を挙げるとすると以下の2つになると考えています。

  1. Django の難しいところをフォローしている
  2. Web開発に必要な実践的知識がいっぱい詰まっている

Django で詰まる一番難しいところを綺麗にフォローしている

Django は公式ドキュメントが大変に充実していて、チュートリアルもしっかりと用意されています。ただやはり英語がメインになるということと、そもそも Djangoフルスタックなフレームワークであるため、そのしきたりに慣れていくのはなかなかに大変です。

本書では Section1 でステップバイステップで簡単なアプリケーションを実装していきますが、データの流れを可視化した図や、引数に対する意図やコメントなどが随所にあり、初めて実装をする人でも理解しやすいような構成の工夫が取られています。またその後のセクションで、テンプレートやORMなどの機能ごとに説明がありますが、こちらにも随所にフロー図やクラス図があり、Django の複雑なクラス構成や設定の構成が視覚的に理解できるようなっています。

Django を始めたてあるいは python をそれほど長く使っていないユーザは Django のクラス構成や設定部分の理解ができず、チュートリアルは写経でできるけれどそれ以上が難しい、ということがあると思っていて、僕もこのフェーズで相当に苦しんだ経験があります。本書はチュートリアルを本質から理解するための良い手がかりとなってくれるでしょう。

正直この図だけでも「あと3年早く読みたかったな」と思いました。

Web開発に必要な実践的知識がいっぱい詰まっている

次に言えるポイントは「Web開発に必要な知識がたくさんある!」という点でしょう。ここであえて[Web 開発に]と書いたのは、本書で扱っている内容が、単に Django の知識にとどまらないからです。それは例えばデータベース・認証認可・セキュリティ・テストなどが該当します。

例えばデータベースの部分を取り上げましょう。もちろんDjangoに関する本ですから、データベースへのアクセスをDjangoでどのように行なうかという内容は、当然触れられていますが、単に使うだけにとどまらず「では実際のSQLはどうなるか」、「そもそもSQLを効率的に実行するためにはどうすればよいか」、更にはクエリ解析によるパフォーマンス確認にまで言及されています。

正直単にDjangoを使うだけならばここまで書く必要がないだろうと思いますが、実際にWebアプリケーションを作る場合には、DBの動作を考えたり、その確認方法を知っていることは大事なことです。これらの知識は単に Djangoチュートリアルをやり、ドキュメントを読んだだけでは身につかない実践的な内容です。

上記ではデータベースセクションを取り上げましたが、他のトピックに関しても同様で、単に Django で実装するにはどうすればいいか? の how to にとどまらない、アプリケーションを動かすための知識が詰まっています。個人的にはテストのセクションでどういう方針でテストを書けばよいかや、ユーザモデルの拡張方法とそれぞれのメリットデメリットなど実装していて悩むところに関して「こういう書き方もあるけど、こういうのあるしいいよ!」のような芝田さんの意見や気持ちが載っている部分がとても良いと感じています。時間差でペアプロしているような(?)感覚に近いかもれませんね。

どういう人に読んで欲しいか

本書は以下のような人に特に向いているなと感じました。

  1. PythonでWebアプリを作りたいなと思っている人
  2. Djangoチュートリアルだけやってみたけれどその後が続かなかった人
  3. pythonの他のフレームワークでエンドポイント1/2個の単純なAPIは作ったことあるけれどそれ以上はない人
  4. Djangoを仕事で使っていてもうちょっとステップアップしたい人

1. PythonでWebアプリを作りたいなと思っている人

これから Web アプリを作ってみたい人には、大変オススメできます。最初は少し大変だと思いますが、Python の勉強をしつつ第一章をこなすだけでも相当力が着くと思いますし、公式ドキュメントを見てあれこれするよりも全体像がつかみやすいですので、その後の学習もスムーズになるではないでしょうか。

2. Djangoチュートリアルだけやってみたけれどその後が続かなかった人

これは本書の特徴 1 で書いたように、難しいところをフォローしている部分がドンピシャで刺さると思います。図や適宜あるコメントなどを参考にしつつ、もう一度チャレンジしてもらえると大変学びがあるのではないかと思っています。

3. pythonの他のフレームワークでエンドポイント1/2個の単純なAPIは作ったことあるけれどそれ以上はない人

これはいつもはwebアプリケーションを作るのがメインではなくて、やったことがあるとすると簡単なレスポンスを返すアプリを Flask / Bottle でちょっと作ったことがあるな、ぐらいの人を想定しています。例えば機械学習の推論用サーバだけ実装、見たいな感じでしょうか。

自分もこのレイヤにいたことがあるのでわかるのですが Django はとてもハードルが高そうに見えるのですよね。実際フルスタックなフレームワークですから、覚えることも多く Flask などのようにさっくりとは作れないので、まあいいかで諦める… ということはママあるのだろうなと想像しています。また Web アプリケーションに必要な知識もそこまで持ち合わせていないので、やりたいことを実現するためにはそもそもどうやったら良いのかわからないし普通どうやってやるんだろう、と思っている人が多いのではないでしょうか。

そういう人がちょっと DB との接続もあってユーザ認証とかもあるアプリをやりたいな! と思った時、Djangoの初心者に向けた内容から実践的Webアプリの内容まで含まれている本書は、まさにピンズドな選択肢と言えるでしょう。諸手を挙げておすすめできます。

4. Djangoを仕事で使っていてもうちょっとステップアップしたい人

最後のこれは私です。なのでみんなが該当するとは言わないのですが、読んでいてなんとなくの理解で使っていた部分や知らない部分など見て相当に勉強になりましたし、Django の勉強の意欲をもらえてとても楽しく読んでいました。(完全に感想になってしまった)

まとめ

どんな人が読んでも、一歩先に進むことができる大変良い本です。

良え本や。

www.shoeisha.co.jp

github にサンプルコードがあるようですので、購入前に中身の雰囲気を知りたい方はこちらも参考にしていただくのが良いかもしれません。*1

github.com

*1:readmeにある「本書のコードが動かなくなってしまうかもしれませんが、Djangoソースコードやリリースノートを読みながら動かなくなった原因を探り、修正する過程で得られる知識は無駄にならないはずです。ぜひチャレンジしてみてください。」というのがとても好きです。この本が、単なる初心者向け入門ガイド書にならなかった理由がよくわかります。

Django Congress 2021 に参加しました!

2021/07/03 に開催された Django Congress に参加しました。とても有意義なお話を沢山拝聴でき勉強になりましたし非常に楽しかったです! ありがとうございました!

以下各発表の自分のメモ書きになります。(スライドは随時追加予定です)

Django 3.2 ASGI対応 - こわくない asyncio 基礎とasync viewの使い所

Junya Fukuda san

  • Awsgi / Async View の説明
    • スライドわかりやすい〜
  • async / await のためには ORM の async 対応も必要
    • 現状はまだできてない。
    • 非同期ORM対応の検討中らしい

note

  • 雑な理解しかしていなかったので参考になった。
  • 非同期ORM、楽しそう。入ると、効率がアップすると思うのだけれど、どのぐらいの改善度になるのかなー?

RLSを用いたマルチテナント実装

Takayuki Shimizukawa san

マルチテナントのメリット

  • 各ユーザが同じリソースを使えるので無駄が少ない

マルチテナントのデメリット

  • データが混濁する可能性がある
  • 気合でデータ混濁を防止するのは無理
  • データ混濁をしないような仕組みづくりが必要

マルチテナントの方法

  • スタンドアロン
    • アプリケーションもDBも別
  • テナント単位DB
    • アプリケーションは同じだけれど、テナント単位で別のスキーマのテーブルを使う
  • シャドーマルチテナント
name DB アプリ
スタンドアロン 分離 分離
テナント単位DB 分離・準分離 共有
シャドーマルチテナント DB共有 アプリ共有

DB準分離

  • メリット
    • スキーマで別れているので混濁が起こる可能性が低い
    • django-tenants のようなライブラリもあるよ
  • デメリット
    • テナント数が増えたときのメンテナンスコストが高い
      • migration をテナント数だけ実行する必要がある
      • 例えば 30[s] の migration で 5000 テナントだと1日以上かかる 😭

DB共有

  • メリット
    • メンテコストが低い
  • デメリット
    • 混濁おこるかもよ → RLS を使って行レベルアクセス権限を設定する

RLSとは

  • row level security (行レベルセキュリティ)
  • 行単位でアクセス権を制御する DB の機能

RLSの仕組み

  • ROLE:
    • データ・ベースのテーブル・行権限を決めるもの
    • 対応の行と、今アクセスしているユーザの role との一致で、行単位でのアクセス権限が制御される。
    • たとえば tenant 列を role として設定して role=1 のユーザでアクセスすると tenant = 1 の行だけしか見えない
  • 権限の扱われ方
    • RLSがあるとテーブルアクセス権と行アクセス権の概念が別に存在する
      • テーブルへの select 権限があっても RLS がないと何も見えない
    • POLICY を設定するとマッチする行だけが帰ってくる様になる

Djangoでどう実現する?

  • Django では DB接続のユーザを切り替えるのは難しい
  • ユーザは変えずに set role を使ってセッションのユーザを付与する
  • Django のライフサイクルに role 設定を仕込む
    • テナント作成時に新しい role を追加
    • post_save の signal でテナント作成
    • middleware でテナント判断
      • ログインユーザに応じた role を付与して query 発行するようにする

通常より気にする必要があるポイント

  • クエリ負荷の上昇は、もちろんある。
    • 負荷対策をよりシビアにする必要がある
  • コネクションを分けるのはできない
    • DBの上限接続数が 100 以上にできない。したがってテナントごとコネクションは難しい。
  • 負荷分散
    • テナントごとにクエリを利用するリソースを分けたい → Citus / Hyperscale 利用を検討する
    • 特定テナントごとに別ノードで分散処理できる (テナント1は node1 で実行などできる)

DB共有デメリット対策

  • role設定忘れ内容に
  • バックアップ方法
    • PITRをテナント単位で行う方法
  • 負荷分散リソース

質疑

  • 単体テストはどうする?
    • 今はコンテナあるので、テストのときもポスグレつかえはOK
  • role を定義しているカラムに index 貼れば速度は改善する?
    • 調査しきれていないが、単に数値を設定するのではなくテキスト付与すると良くない

Note

  • 各設定方法のメリットデメリットが整理されていてありがたい。
  • テナント形式のアプリはたまにあるので参考にしよ。

Djangoでのプロジェクトだって型ヒントを運用出来る!

みずき-san: https://speakerdeck.com/mizzsugar/djangodefalsepuroziekutodatutexing-hintowoyun-yong-chu-lai-ru

speakerdeck.com

型ヒントのよいところ

どこから始める?

  • django のコード部分は django-stubs の知識が必要でハードルが高い
  • pure な python 部分から

git pre-commit フック

  • メリット
    • CI が不要
  • デメリット
    • コミット内容と関係ない部分もエラーになる
    • エラーがあるとき、強制的に commit をする方法もあるがオオカミ少年的になる
  • 結局利用しないことに決めた
    • 途中から導入したゆえの大変さ
    • 最初から入れていたらやっていたかも

--strict option

  • mypyはデフォルトでは4つを指摘する
  • strict にするとより細かい内容も指摘する
  • django で strict は必要?
    • django のコードと strict の相性が悪い
      • これは解決したみたい。
    • デフォルトの内容でも十分と判断して strict しないように

CI をいつ入れる?

  • django 関係ない utils module にたいして type がついてから

詰まったポイント

  • AbstractUserModel の継承時の問題
    • [memo] おそらく class の継承全般で発生する問題かな?
  • field と同一名の method 定義
  • related object does not exist がない
  • Queryset.value_list で django-stubs の型が django から消えてる

質疑

  • チームからの反対はなかった?
    • もともと別のプロジェクトに型をつけていた。その時のメンバーは好意的にやってくれていた。
    • 次のプロジェクトにも入れていこうという流れにつながった

Note

  • 型設定のチーム導入の話まで踏み込まれていて学びがおおい
  • mypy 使えてないのでちいさいとこからこつこつ入れていこうと思いました。

Django管理サイトをカスタマイズする前に教えてほしかったこと

akiyoko-san: https://speakerdeck.com/akiyoko/how-to-customize-admin-djangocon-jp-2021

Django 管理サイト

  • みんな使ってる管理サイト
  • 困りごと
    • カスタマイズが大変という意見が多い

基本仕様

  • ログイン条件
    • is_staff / is_active が true
  • permission
    • モデルごと・操作ごとの permission が必要

カスタマイズの難易度

  • かんたん
    • django の用意してくれているカスタム方法を使うやり方
    • 全体をかえる → AdminSite
    • モデルごと → ModelAdmin
  • 難しいこと
    • template のカスタマイズ
    • css のカスタマイズ

template のカスタマイズ

  • 優先度が高いディレクトリに編集したいテンプレートを設定する
  • 直したい部分だけ継承して編集・場合によっては全体まるっと持ってくることも可能
  • [tips] 実際にどのようなテンプレートが使われているかは django-debug-toolbar の template から確認できる

CSS のカスタマイズ

  • 静的ファイルの優先順位を知る
    • template 同様に、ファイルの優先順位があるので、自分が設定したいものが上に来るように適宜設定する。
  • 全体の編集:
    • 管理サイト本体の extrastyle block を override して設定
      • extrastyle は main の css のあとに読み込まれるので優先度高く読み込まれる
    • 設定方法
      • 新しいファイルを読み込むように overide
      • 静的ファイルの場所が優先度が高くなるようにしておく
  • モデル部分のみの編集:
    • model admin の media.css を利用する

admin site のテスト方法

  • そもそも断片的なコードしかないのでどういうテストをすればよいかが自明ではない

テストの方法

  • lxml を使ったテスト
  • Selenium Driver をつかったテスト
    • Selenium で admin 管理画面をテストする専用のテストケースクラスがある
    • スクリーンショットを作成して保存するとかもある
    • 実行時間は長めになるので注意

Note

  • django提供のカスタムばかりメインでやっていたので template / css のカスタム方法の体系的な指針をもらえて嬉しい。
  • 管理画面の可能性を感じた。

未経験者のDjangoでの個人開発

haru-san: https://docs.google.com/presentation/d/1gm0kGhoNLJ8fe4qtdOg5y6kzZk8ZRjBEnZQ9sNui2Hc/edit#slide=id.ge1c975fe8d_0_1

docs.google.com

大変だったこと

  • ORM
  • 認証周りなどなど

なんでそうなっちゃったか

  • コードと挙動の関係性理解が欠如しているママまえに進んでいた
    • Django のコードと実際に発行される SQL の関係性 ...etc
  • 参考にして良いものがわからなかった
  • わからないことがいっぱいあって焦っていた。

反省点

  • コピペでチュートリアルをやっていた
    • 中身を理解せず、動かすことに注力していた
    • 動かす楽しさは大事だけれど、長期的に見て中身を理解することが大事
  • エラー内容の本質理解の欠如
    • なんとなくで理解していた
    • エラー文をちゃんと読まずに表面的対応になってた

良かったこと

  • 設計の意識
  • git を CLI から使う

反省から学んだこと

  • 非公式の情報を参考にするときは以下を気をつける
    • 前提条件が欠かれているのか
    • 中身を自分が理解できる説明か
  • 公式ドキュメントを大事にする
  • 初歩を大事にして、少しづつ応用へと向かっていく
  • 疑問点をそのままにしない

学習はとにかく時間を書けるしかない

質疑

  • なんで django にしたの?
    • 友達がやっていたのでやってみた
  • 振り返りの考え方ができるのはなんでですか?
    • スポーツをやっていて、その中で人と比較して自分を省みることをまなんだ
  • どういう本があったら嬉しい? どういう内容があると嬉しい?
    • ネットで探して出てくる情報は断片的
    • 実際の設計・構成がどうなっているかがわからなかった
      • Django でクリーンアキーテクチャをやろうと思ったときどうやればいいか
      • 具体的にアプリ設計をするときどのような命名ディレクトリにするべきなのか
  • socail auth の設定はどのようにやった?
    • 考えてもわからなかったので質問して解決した
    • 質問をするときに情報をまとめる作業で解決することもあった
  • 今後の興味は?
    • チーム開発のやり方・テストコード
    • 機械学習もやってみたい

note

  • 何ができていなくて、何をしたらいいのかが言語化されていて素敵
  • 17歳すごい

Securing Django Web Applications

Gajendra Deshpande san: https://speakerdeck.com/gcdeshpande/securing-django-applications

speakerdeck.com

  • xml file じゃなくて json にしましょう
  • 英語聞くのに必死でメモできず…

Note

  • こういう情報はどうやって仕入れているのかなーと気になったのですが聞けず… mmm
  • 資料の量がすごい。読み返そう。

Django & Celery in production

Masataka Arai san: https://speakerdeck.com/massa142/django-and-celery-in-production

speakerdeck.com

タスクキューとは

  • producer
    • task を作って broker へとパスする
      • task: 非同期で実行する処理のひとつのまとまりのこと
      • queue: task を入れる入れ物
  • broker
    • もらった task を queue に入れる
    • queue に入っているタスクを consumer へと渡す
  • consumer
    • 実際に task をおこなう (celery worker)

タスクキューにはいいとこ悪いところがあるのでそれを理解して使う必要がある (ex: メール送信などなど)

  • メリット
    • 時間がかかる処理を非同期に逃せる (リクエスト早くなる)
    • 処理が別れているのでスケールしやすい
    • エラーがあってもリトライできる
  • デメリット
    • 複雑化
      • 監視・ログの考え事がふえちゃう
    • 処理の遅延が大きくなる可能性がある

celery 以外のやり方もあるよ

  • RQ
  • FaaS: lambda と SQS

気をつけポイント

  • モデルインスタンス渡しがちだけど、古いデータになることに注意
    • 実行時に最新の instance を取得したいのであれば pk とかを渡すように
  • テスト時には同期的に実行するオプションを入れる

リトライ設計の心得

  • 復帰可能なエラー以外は投げっぱなしにする
  • 復帰可能なものに関しては…
    • 無限にくりかえす
    • 冪等性を担保して何回も実行する
  • 運用でカバーする必要があるものに関しては DB に入れる
  • 目的

タスクへの設定方法

  • 単純なリトライ
    • autoretry_for
  • 複雑なリトライ
    • retry_when_retriable に自分で関数を設定
    • だめだった理由ごとにリトライの方法を書いていく
      • mysql のロック などなど
    • エラーハンドリングの育て方はどうするんだろう

ロギング

  • django-celery-results
    • celery の task protocol = 2 だとネストした dict の深度が 3 になるので注意
  • 運用カバー
    • django-admin から以上設定のものを再起動できるようにしておく
  • 監視
    • celery inspect を使って待ってるタスクを見る
    • 実行待ち時間が長くなると slack に投げる、など

本番環境の更新

  • task 更新時
    • タイミングのずれで昔の引数で呼び出される可能性がある
    • 引数には必ずデフォルト値を設定 & kwargs でハンドリングできるようにしておくことで新旧の呼び出しに対応する
  • task 削除時
    • 昔のサーバーから call される可能性がある。
    • celery はそのままで app サーバーだけ更新する。

Note

  • ナイーブに app と同時に celery server も更新すると良くないのとか考えてなかった。CI 組み込むときも気にしないとな。

理解して使いこなすDjangoのForm機能

フォームの vaidation を少し直すとなったときでもいろんなパラメータがあってよくわからない。

  • django shell をつかおう
    • form を shell から見ることで理解が深まる & テストコードも自然に書ける

Form の構成要素

  • bound field
    • html の作成を担当
  • widge
  • error
    • form.errors にエラーが代入される
    • form から見ると widge の名前が key に errors の配列 (List[Error]) が value に入って取得できる
  • 複数の field にまたがった validation をする場合
    • form.clean に記述する
    • super().clean() を call して各要素の validation を終わったあと validation がおわったデータを使って validation のロジックを記述する
    • ここでエラーになると none-field error になる
    • 特定の key にエラーを入れることもできる (add_errors)
  • 一つの field で validation
    • form のコンストラクタに validators を追加する

Note

  • form の構成から細かく説明いただいていて大変参考になる。

スポンサーセッション

  • (株)日本システム技研さん
    • 設立 昭和51年7月
    • 会場コストのスポンサー。大変ありがたい。
    • 2014年から会社として Django を使おうという流れできていて、当時と比べると今はたくさん日本語の情報が出ていて、隔世の感とのこと。そうなのかー!

まとめ

いろんな新しいことをしれてとても楽しかったです! 開催運営のみなさまありがとうございました!

[tips] factoryboy で作成したモデルに type hint をつける方法

factoryboy は python 用のモックアップデータを作成するライブラリです。 Django の model object にも対応していて required な field を動的に生成したり (ex. user_0001 みたいに連番にしたり, アルファベットをランダムに選んだ文字列にしたり...)、relation を持つ object を relation 先も一緒に作成できて便利です。

例えば以下のような user / article model があったとしましょう。

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=8, unique=True)


class Article(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    body = models.TextField(null=True, blank=True)

この時適当な article object を作りたいとなると

  1. 作成者になる user を作成して保存.
  2. ブログのタイトルを適当に生成
  3. 1,2 をつかって article を作成

という3ステップを踏む必要があります。

user = User(name='foo')
user.save()
article = Article(author=user, title='bar')

これを見ると例えば

  • いちいち article を作るのに user を作成するのは面倒なので generate_article を作りたくなる
  • article の初期値は自分で弄りたいので, 引数に user / title / body は含めたい

と思うことでしょう。関数にするならば以下のような形でしょうか。

def generate_article(user = None, title = None, body = None):
    if user is None:
        user = User(name='foo')
        user.save()

    if title is None:
        title = 'bar'

    return Article.objects.create(user=user, title=title, body=body)

さあこれでOK! と思うかもしれませんが、このコードには問題があります。そう User の name は unique 制約が付いているのです。したがって user=None で2回 generate_article を呼び出すとエラーになってしまいます。困りますね。 User を作成する時 name を何らかのランダムな文字列にする、というようなコードを書く必要がありますが、それらをいちいち書いていると大変です。

こういうモックデータを作成するときに使えるのが factoryboy です。factorybody で article を作成するコードを実装すると以下のようになります。

from factory.django import DjangoModelFactory
import factory
from factory.fuzzy import FuzzyText


class UserFactory(DjangoModelFactory):
    class Meta:
        model = User
        django_get_or_create = ("name",)

    name = factory.Sequence(lambda n: f"u-{n}")

class ArticleFacotry(DjangoModelFactory):
    class Meta:
        model = Article
    
    author = factory.SubFactory(UserFactory)
    title = FuzzyText(length=12)

簡単に説明をすると Sequence では作成のたびに関数が呼び出されて引数の n が増えていきます。また FuzzyText ではランダムに文字列が, SubFactory は relation を持つモデルのときに、指定された Factory (上記の場合だと UserFactory) で作成されたモデルが設定されるようになります。このように、作成したタイミングで動的に属性指定をしてモデルを作成する手伝いをしてくれるのが factoryboy です。

ここから article を作るのは以下で終わりです。

article = ArticleFacotry()

簡単ですね。また author だけ指定したい! という時には __init__ に author を渡して上げればOKです。簡単ですね。

goto = UserFactory(name="goto")
goto_article = ArticleFacotry(author=goto)

assert goto_article.author == goto # True

このように便利なのですが1点困るのは pycharm のような IDE で factoryboy の constructor で作成された object が作成対象の object としてみなされないことです。なので普通に作成してインテリセンスを利かせてもほしい物が出てきません。

f:id:dette:20210702004733p:plain
factoryboy object だと思い込んでいる(それはそう)なので、サジェストが django model のそれになっていない。

これを解決する方法の一つとして紹介されていたのが「TypeVar T を定義して, T を返すような class method を用意する」方法です。(ref: https://github.com/FactoryBoy/factory_boy/issues/468#issuecomment-759452373 )

from typing import Generic
from typing import TypeVar
import factory

T = TypeVar("T")

class BaseFactory(Generic[T], factory.django.DjangoModelFactory):
    @classmethod
    def create(cls, **kwargs) -> T:
        return super().create(**kwargs)

使う方ではこんな感じ。

class ArticleFactory(BaseFactory[Article]):
    class Meta:
        model = Article

    author = factory.SubFactory(UserFactory)
    title = FuzzyText(length=12)

元の実装と違うのは BaseFactory に [] で返り値のクラス T を指定している点です。こうすることで T に与えられた object が create の返り値とみなされるため IDE で返り値は django model の instance だと思ってくれます。

# __init__ (constructor) は使わずに
# article = ArticleFacotry() 

# `create` を使う
article = ArticleFactory.create() 

f:id:dette:20210702004633p:plain
ちゃんと理解してもらえて嬉しい

author のほうもこのとおり。

f:id:dette:20210702004452p:plain
vvv

というわけで factoryboy の tips でした。

docker pull をすると toomanyrequests でエラーになるやつ

docker pull をすると toomanyrequests でエラーになる

ERROR: Preparation failed: Error response from daemon: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit (executor_docker.go:188:2s)

原因はエラーメッセージにも書いてますが dockerhub からの pull 回数の制限に引っかかったこと。ちょっと前から制限が追加されたようでした。

matsuand.github.io

まずこの制限によって pull が出来ないようになっているかは API へリクエストを送って返ってくる status で確認できます。以下の shell を実行しましょう。 jqcurl が必要なので適宜 install しましょう。

TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest

こんな response が帰ってきます。

HTTP/1.1 200 OK
content-length: 2782
content-type: application/vnd.docker.distribution.manifest.v1+prettyjws
docker-content-digest: sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020
docker-distribution-api-version: registry/2.0
etag: "sha256:767a3815c34823b355bed31760d5fa3daca0aec2ce15b217c9cd83229e0e2020"
date: Sat, 22 May 2021 15:01:23 GMT
strict-transport-security: max-age=31536000
ratelimit-limit: 100;w=21600
ratelimit-remaining: 45;w=21600

中にある ratelimit-remaining がこのマシン (おそらくIPごと) に対して割り当てられている残りの pull 可能回数です。この例だと残り回数は 45 になっています。先のエラーになっている場合ここが 1 とか 0 になってるはずで、回数制限にかかっていることが確かめられます。

対策方法

例えば CI で docker pull を使っている場合を想定します。この場合毎回 0 から build すると都度 dockerhub へのリクエストが発生してしまいます。これを別の registry へ都度 image を push しておいて build 時には前の image をキャッシュとして利用する戦略を使うことで dockerhub へのリクエスト数を減らすことが出来ます。その代わり代わりレジストリへのアクセスは増えるので場合によっては (たとえば ECR など)、お金がかかる用になるのは注意点。

以下は gitlab ci の形式の例です。

.build-and-push-ecr:
  stage: build
  script:
    # Pull cache docker image from AWS ECR
    # ここは場合によっては gitlab-registry でも良いと思う 
    - docker pull ${ECR_REPO}:latest || true

    # pull image を cahche として指定して build
    - docker build --cache-from ${ECR_REPO}:latest -t ${CI_PROJECT_NAME}:latest -f ${DOCKERFILE} .

    # ECR へ push
    - docker tag ${CI_PROJECT_NAME}:latest ${ECR_REPO}:latest
    - docker tag ${CI_PROJECT_NAME}:latest ${ECR_REPO}:${CI_COMMIT_SHA}
    - docker push ${ECR_REPO}:latest
    - docker push ${ECR_REPO}:${CI_COMMIT_SHA}
  variables:
    DOCKERFILE: ./docker/django/Dockerfile
    ENV_PATH: 
    ECR_REPO: # your-ecr-repo-name

余談: azure だと関係ないかもしれない

Azure のインスタンスの場合、先の ratelimit-remaining を確認すると値が設定されていないようでした(n=2)。 dockerhub の公式を見ると一部認められた publisher 等は制限なしになっているみたいなので azure と dockerhub とがパートナーシップを持っているからなのかな?

I don’t see any RateLimit headers If you do not see these headers, that means pulling that image would not count towards pull limits. This could be because you are authenticated with a user associated with a Legacy/Pro/Team Docker Hub account, or because the image or your IP is unlimited in partnership with a publisher, provider, or open source organization.

一応 Azure 公式には認証して pull しようねという記載があるので、上記現象はあまり pull を行っていないからたまたま ratelimit が表示されていないだけかもしれないです。謎。

最初の手順として、現在、ビルドまたはデプロイのワークフローの一環として Docker Hub からパブリック イメージをプルしている場合、匿名のプル要求を行うのではなく、Docker Hub アカウントを使用して認証することをお勧めします。

docs.microsoft.com