Open

Amena開発日記

169
ピン留めされたアイテム

ピン留め

サービス構想

  • 3D Photo生成サービス
  • Web UIから静止画をアップロードするだけで簡単に3D Photoを生成

※3D Photo↓

開発用Organization

https://github.com/amena-dev

アーキテクチャ

主に使いたい技術

  • MQ
    • オリジナル静止画のEnque
    • 3Dフォトグラフィ生成エンジンによるDeque
  • IaC
    • Terraformを用いたインフラ構築(AWS)
  • Kubernetes
    • Amazon EKSによるコンテナオーケストレーション
    • オートスケーリング機構

amena

以下の勉強用プロダクト

  • オートスケーリング機構(k8s)
  • ロードバランシング機構(k8s)
  • MQ (Amazon SQS, NATS, kafka... など)
  • IaC (Terraform)

サービス構想

3Dフォトグラフィ生成サービス

  • 静止画アップロードおよび生成された3Dフォトグラフィの確認までをWebUIで完結させたい

主に使いたい技術

  • MQ
    • オリジナル静止画のEnque
    • 3Dフォトグラフィ生成エンジンによるDeque
  • IaC
    • Terraformを用いたインフラ構築(AWS)
  • Kubernetes
    • Amazon EKSによるコンテナオーケストレーション

まずはPoCと思ったが、核となる3Dフォトグラフィ生成部は既存の実装を使う(≒すでに効果は実証されている)のでスキップ、まずは全体のインフラ構成を詰める。

余談だが、GitLabの「Project」にあたる機能がGitHubでは「Organization」で使えるのは良い発見だった(Zennインスパイア)

基本方針として、このプロダクトで習得したいのは主にインフラ周りの技術なので、アプリケーション層はそこまで凝りたくない。逆に言えばアプリケーション層をシンプルに保てるインフラ構成を目指したい。

ログイン機能は面倒臭くて付けたくないので、ざっと以下フローでアップロード〜成果物確認までを行う方針で検討。もしかするとDBも要らなくなるか?

  1. クライアント:静止画をフォームからアップロード
  2. システム:リクエストをEnqueし受付IDを発行
  3. クライアント:システムから受付IDを受け取ってCookieへセット
  4. システム:成果物をS3 Bucket://{{受付ID}}に格納(数分掛かる見込み)
  5. クライアント:Cookieの受付IDを元に一定周期で成果物が出来てないか問合せ
  6. クライアント:成果物ができていたら署名付きURl経由でS3からダウンロード

こうするとユーザアカウント情報管理が不要なのでシステムをだいぶシンプルに保てる。Cookieを削除すると成果物を取りにいけなくなるが、まあそれは仕様として(間違って削除しても再度リクエスト投げれば良い。)

あと、何気にシェアードナッシングを達成できるので冗長化にも強そう。

いや、上記の構成だと一人のユーザが同時にいくつも解析リクエストを投げられてしまうか。
IP単位でスタック数を制御しようにも複数台のPCでこられたらどうしようもない。

Googleアカウントでログインし、そのIDごとに一度にスタックできる解析リクエスト数を制御しようか(例えば一つのアカウントにつきスタックできる解析依頼は3つまでとか)

その場合別途DBでアカウント毎の解析進行数を管理?それともMQを見てそれぞれの依頼に紐づいているアカウントIDを確認できる?その辺りは調査。

以下のフローにしよう。DB使うのは本意ではなかったがまあセキュリティの面で仕方ないか。

  1. クライアント:Googleアカウントでログイン
  2. クライアント:GoogleのAuthTokenと共に静止画をフォームからアップロード
  3. システム:リクエストをEnqueしGoogleアカウントIDと受付IDをDBへ保存
  4. システム:成果物をS3 Bucket://{{受付ID}}に格納(数分掛かる見込み)
  5. クライアント:一定周期でログイン中のGoogleアカウントに紐づいている成果物が出来てないか問合せ
  6. クライアント:成果物ができていたら署名付きURl経由でS3からダウンロード

参考:バックエンドサーバを用いて認証する

## Google APIクライアントライブラリを使用する
本番環境にて Google IDトークンを検証するには、Google API Client Libraries (例えば、Java、Node.js、PHP、Python)のいずれかを使用することが推奨される方法です。

まあアカウント使う仕様になると前から考えてた課金機能も実現しやすくなるので良い。

MQについて、取り敢えずシンプルそうなAmazon SQSの方針で検討してみる。

なお今回は費用面は一切気にせず好きに作ってみる。最悪月10くらい掛かっても良い。

Amena初期構想アーキテクチャ図

draw.io延々弄ってられるな...

terraform apply 時に以下エラー発生

Error: error creating EKS Cluster (eks-dev-cluster): InvalidParameterException: unsupported Kubernetes version                                             
{                                                                                                                                                          
  RespMetadata: {                                                                                                                                          
    StatusCode: 400,                                                                                                                                       
    RequestID: "{{ID}}"                                                                                               
  },                                                                                                                                                       
  ClusterName: "eks-dev-cluster",                                                                                                                          
  Message_: "unsupported Kubernetes version"                                                                                                               
} 

variables.tfcluster_version = "1.12"cluster_version = "1.18"に直したら通った。

怒られた
Error: Error creating launch configuration: ValidationError: The key pair 'KEY' does not exist

aws_key_pairを使ってterraformでkey_pairを作るようにして通った。
terraformまともに弄るの初めてだけどだいぶスラスラ設定読めるようになってきた、これは良い。

resource "tls_private_key" "eks-private-key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "eks-key" {
  key_name   = var.key_name
  public_key = tls_private_key.eks-private-key.public_key_openssh
}

〜〜〜

resource "aws_launch_configuration" "lc" {
  associate_public_ip_address = true
  iam_instance_profile        = aws_iam_instance_profile.eks-node.id
  image_id                    = data.aws_ami.eks-node.image_id
  instance_type               = var.instance_type
  name_prefix                 = "eks-node"
  key_name                    = aws_key_pair.eks-key.key_name
〜〜〜

初Apply!!これはめでたい!!
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

$ terraform output eks-configmap > ./outputs/eks-configmap.yaml
$ terraform output kubectl-config > ./outputs/kube-config.yaml

kubectl apply Done!!! 正常にクラスタ作成されてることをコンソールで確認済み!!やった!!
しかしNodeが立ち上がってない...?

コンソールに以下表示。contextあたりが怪しそうか。

Your current user or role does not have access to Kubernetes objects on this EKS cluster
This may be due to the current user or role not having Kubernetes RBAC permissions to describe cluster resources or not having an entry in the cluster’s auth config map.

いや、IAMか

上記の問題でEKSコンソールからは見れないが、EC2コンソールからはちゃんとnode立ち上がっているのを確認できた!!!

kc get nodesでもEKSコンソールからも立ち上がってるnodeが見えないが、EC2コンソールで確認するとEKS由来のEC2インスタンスは立ち上がってる。contextやIAMが関連してると思うが良く分からん。

kc get nodesで取れた!!!!!!!!!!!!

しかし依然WEBのEKSコンソールからは権限不足でnodeが見えない。ちゃんとIAMも付与出来てるっぽいんだが...。

作成したnodeにpodが載った!!!これはめでたい!!!やった!!!

$ kc logs -f myapp-pod                                                                                  
Hello Kubernetes!

tf applyまでのフロー確立できたからあとは早い
残:CW, S3, SQS, Route53, RDS

開発初期段階で不要なcwとroute53以外のインフラリソースはひとまず仮設定でデプロイできた。
あくまで仮設定なので、これから始めるWEBバックエンド実装で同時にリソースの細かい設定も詰めていく。

https://github.com/orgs/amena-dev/projects/2#card-52255704

完全OSSにしようかと思ってたがさすがにセキュリティ面が怖いのでterraform設定はprivate repositoryへ

TODO tfsec導入

バックエンド実装 start

前から気になっていたNode.jsのFastifyフレームワークで実装してみる

こんな感じのディレクトリ構成に

src
├── 3dphoto
│   ├── 3dphoto.controller.ts
│   ├── 3dphoto.routes.ts
│   └── 3dphoto.service.ts
├── common
│   └── interfaces
│       └── response.interface.ts
├── router.ts
├── server.ts
└── version
    └── version.routes.ts

AuthorizationとErrorHandlingのフローをさくっと組み立てた。これから本質のロジック実装に入る。

src
├── 3dphoto
│   ├── 3dphoto.controller.ts
│   ├── 3dphoto.routes.ts
│   └── 3dphoto.service.ts
├── common
│   ├── authorization.ts
│   ├── error
│   │   ├── error.class.ts
│   │   └── error.handler.ts
│   └── interfaces
│       └── response.interface.ts
├── router.ts
├── server.ts
└── version
    └── version.routes.ts

コンテナ化して動かしたfastifyだとちゃんとポートフォワード設定してもレスポンス返ってこない...と2時間くらい詰まってたが、どうやらコンテナ化するときはlisten時に0.0.0.0を設定しないといけなかったらしい。

https://www.fastify.io/docs/latest/Getting-Started/#your-first-server

Before

server.listen(3000, (err, address) => {

After

server.listen(3000, "0.0.0.0", (err, address) => {

TYPEORMにEntity認識されないなぁ...と悩んでたけど、entitiesをdist向きにするのを忘れてただけだった
Before

    "entities": [
       "src/entity/*.ts"
    ],

After

    "entities": [
       "dist/entity/*.js"
    ],

マイグレーションまでこれでうまくいった

    "entities": [
        "dist/entity/*.js"
    ],
    "migrations": [
        "src/migration/*.ts"
    ],

input_image table作成してapi側から参照するところまでできてる

aws-sdkの導入もdone

backendサーバーからSQSへEnqueue出来た!これは良い

あとは仕様書どうりにAPI実装やるだけ

SQS面白いなぁ

backendサーバからS3へimage画像を保存できた。ここまで来たらこっちのもん。

本当に、本当に今更だけど、↓は完全にあることを見落としてた

いや、上記の構成だと一人のユーザが同時にいくつも解析リクエストを投げられてしまうか。
IP単位でスタック数を制御しようにも複数台のPCでこられたらどうしようもない。

Googleアカウントでログインし、そのIDごとに一度にスタックできる解析リクエスト数を制御しようか(例えば一つのアカウントにつきスタックできる解析依頼は3つまでとか)

その場合別途DBでアカウント毎の解析進行数を管理?それともMQを見てそれぞれの依頼に紐づいているアカウントIDを確認できる?その辺りは調査。

そもそも進行してる解析をDBで管理しなくてもS3のinput bucketを見れば良いだけの話だろう...完全に盲点だった。なんで今のいままでこんな簡単なことに気がつかなかったんだろう。

諸々設計修正する。

修正Done.これでだいぶ設計もシンプルになった

コード修正もDone

input取得API Done

input削除API Done

output取得API Done. これでweb-backend実装はほぼ終わり。
次は3DPhoto生成エンジンを作り込む。

現状のディレクトリ構成。ほぼ1日でここまでしっかり構成作り込めたのは良い。
あとは意外にS3とSQSとの連携が簡単だった。

src
├── 3dphoto
│   ├── 3dphoto.controller.ts
│   ├── 3dphoto.routes.ts
│   └── 3dphoto.service.ts
├── common
│   ├── authorization
│   │   └── authorization.ts
│   ├── aws
│   │   ├── aws.s3.ts
│   │   └── aws.sqs.ts
│   ├── config
│   │   ├── config-local.json
│   │   ├── config-production.json
│   │   ├── config.json
│   │   └── config.ts
│   ├── error
│   │   ├── error.class.ts
│   │   └── error.handler.ts
│   └── interfaces
│       └── interface.response.ts
├── router.ts
├── server.ts
└── version
    └── version.routes.ts

8 directories, 16 files

あと何気にRDSいらなくなったことで全体的な工数がガクッと減った。良い気づきだった。

RDS除外

なお3DP=3DPhotography

開発用S3バケットの様子

開発用SQSの様子

突然デザイン欲が降ってきたのでロゴを書いた→ピン留め

愚直にビルドしたらモデル含めてimageが5GBもあるな...これからダイエットする

モデルを含めない場合でも4.16GBあるな、おかしい。
pytorchとかcuda関連ツールで容量食ってる?

ビンゴ、pipパッケージのインストール手順省くと1.48GBまでに。

推論エンジンのソースコード削ったら876MB。
大体内訳がわかってきた。

そして876MBはちょうどベースのpython imageの大きさ。
こんなでかいのか。

python3.7-slimで1GB減った

alpine導入するとさらに80MB減らせる見込みだけど、opencvのビルドエラーで面倒くさいことになってる。まあ妥協してpython3.7-slim使う方針で行こう。残りは必須なライブラリだから4GB imageはのちの課題としてひとまず妥協。

流石に5GBは大きすぎる、もう少し内訳を探る

56K     /usr/local/lib/python3.7/site-packages/proglog                                                                                                     
248K    /usr/local/lib/python3.7/site-packages/wheel                                                                                                       
292K    /usr/local/lib/python3.7/site-packages/certifi                                                                                                     
408K    /usr/local/lib/python3.7/site-packages/requests                                                                                                    
444K    /usr/local/lib/python3.7/site-packages/tqdm                                                                                                        
504K    /usr/local/lib/python3.7/site-packages/transforms3d                                                                                                
516K    /usr/local/lib/python3.7/site-packages/idna                                                                                                        
872K    /usr/local/lib/python3.7/site-packages/urllib3                                                                                                     
1.1M    /usr/local/lib/python3.7/site-packages/moviepy                                                                                                     
1.1M    /usr/local/lib/python3.7/site-packages/tifffile                                                                                                    
1.6M    /usr/local/lib/python3.7/site-packages/chardet
2.7M    /usr/local/lib/python3.7/site-packages/setuptools
5.0M    /usr/local/lib/python3.7/site-packages/imageio
8.8M    /usr/local/lib/python3.7/site-packages/pip
9.9M    /usr/local/lib/python3.7/site-packages/vispy
11M     /usr/local/lib/python3.7/site-packages/networkx
25M     /usr/local/lib/python3.7/site-packages/numpy
35M     /usr/local/lib/python3.7/site-packages/matplotlib
35M     /usr/local/lib/python3.7/site-packages/torchvision
43M     /usr/local/lib/python3.7/site-packages/cynetworkx
65M     /usr/local/lib/python3.7/site-packages/scipy
1.4G    /usr/local/lib/python3.7/site-packages/torch

小手先のテクニック使っても4GB程度が限界だった...いったんissue切ってとりあえずこのまま進む

pythonからSQSとS3連携できた。あとはこの仕組みを既存の推論エンジンに組み込む。

せやな

I think direct writing on S3 using multipart upload is a good solution. It will upload quickly and the process will be fast. As of now we are saving the output video on a local file and then that file is uploaded to S3 which takes time (1. video rendering 2. uploading 3. Deleting the video) and then respond to the client.

バックエンド側でSQS EnqueからS3画像保存までラグがあって、クライアントがMessage受信してS3からDLする頃にS3にオブジェクトがないという現象が起きていた。そうか、このためにSQSのMessage配信ラグ設定があったのか、学び。

delay sec = 1でうまくいった。にしても、Enque(from: backend) からDeque & S3 DL (from: 解析engine)までにこれまで1sもかかってなかったってことか、予想以上に早いな

バックエンドAPIへ画像POSTしたらSQS経由で同時に解析エンジンが動き出すところまで動いてる

REST APIでポチッとするだけでほぼ同時に別コンソールの解析エンジンが動き出す体験、良い。

API POST → 解析エンジンが動作 → S3へ成果物をOutput
まで、若干荒削りだけど動いた

docker runで立ち上げたpythonのprint内容がdocker logsで見えないなと思ったら、どうやらpython -uオプションで起動してあげないとstdoutをバッファしちゃうっぽかった。

Dequeしたあと処理で何かしらエラーが起こったらS3のoutputバケットにerror.jsonを吐くようにした

文献漁ってると解析エンジンのimageで10GB以上ある場合もあると知った。
よし4GBで妥協しよう()

【実装】Webフロントエンド Doing
フロントは実際作っていきながらインスパイアでUIデザインするのが好きだから、設計フェーズを飛ばす(ロジック面もまあ簡単なAPI叩くだけだから)

と、ここで解析エンジンをdockerで動かすとvispyがqt5を要求してクラッシュする問題がでた。
vispy & EGLでどうにかできないか探り中。

うーん、EGLはGPUレンダリングか。dockerでvispy動かすにはどうすれば。

engine_1  | RuntimeError: Could not import backend "EGL":
engine_1  | Could not initialize

osmesaで動かしたが以下エラーが発生

error: attributeerror("'looseversion' object has no attribute 'version'")

手元のmac開発機だとcuda入らないので、g3sインスタンスにcudaやnvidia driver入れてエンジン単体で動かすと正常に動いた。GPU前提でDockerfile修正してエンジン開発はfixさせよう。

よし!g3sインスタンス&DockerでエンジンのSQS, S3連携まで動かせた。

ロゴをアップデートした

ロゴ修正。だいぶ洗練できた。

ロゴアップデート、さらにイメージカラーを追加。ロゴと文字のバランスが難しいなぁ...。

あとインフラコストを抑えるために、解析Queueが0の場合はkubeのオートスケーリングで解析Node・Podも0にしたい

フロントデザイン脳内で色々fixしてきた。土日で一気に作り上げよう。

だめだフロントデザインにこだわり過ぎてるな...ここは次の休みでもうfixさせよう

なるほど、イベントに対して並行処理が必要な場合「イベント発火→SNS→複数SQSへ配信」というフローがあるのか。

https://dev.classmethod.jp/articles/cross-account-sqs-message-send-patterns/
"SNSを挟むべき代表的なユースケースとしてFanoutパターンがあります。一つ目イベントに対して並行して処理を行う場合には、SNSをメッセージのハブの様に使用してPub/Subモデルのアーキテクチャを採用してPublisherで複数メッセージを送信せずにSNSに任せましょう。"

あーSNS挟みたいいいい。しかし今回のシステムではメリットなさそうか。。。

これまでS3へのinput画像配置とSQSへの解析依頼putはバックエンド側でやってたけそ、そもそもSQSへのputはS3のtrigger機能使えば自動で行けそう?要検討。

Redux入門すんぞ

知見upadte:FIFO, LIFO

いやあくまでReact HooksはFunction ComponentでStateを使えるようにするだけってやつか?
もう面倒臭くなった(本来アプリ層にここまで時間をかけたくなかった)から、Class Component & Stateを酷使してAPIとフロントの連携としよう。

get inputsのAPI連携までできた

get outputsの連携も

とりあえず全APIとフロント表示との連携はできてる。
あとは細かいフロントのエラーハンドリングやログイン機構を作り込む。

しかしtrigger機能は一定も負荷を超えるとラグが酷くなるという話も聞くんだよなあ、要検証

ALB Ingress Controllerなる便利なものがあるのか。。。

ALBのリソース管理をTerraformでやるかk8sでやるか考えないとなのか。もともとTerraformでやる想定だったが、ここはインフラとアプリケーション層の接合部だからなぁ...。

見えてきた

ALB経由でフロントエンド疎通や!!!!

おら!!!!!k8s設定完了してfront, back, ai engineの全疎通取れたぞ!!!!!!!これでサービスとして初めて本番乗った!!!!あとは細かいところ詰めるぞ!!!

backend podやfrontend podのCPU使用率ベースのスケーリングはあっさりできたけど、AI EngineのSQS Queue数を見てのスケーリングが一筋縄ではいかないなぁ...もう少し格闘。

うーん、ALB ingress導入まではできたんだが、External DNSでamena.uehr.coのレコード連携までうまくいかないなぁ...。

できたああああああああ!!!

ssl化もばっちり

初期ロードマップで予定していたタスクは全て完了。あとは細かい仕様やAIエンジンの性能改善に注力する。

GPU対応 Doing

  • GPUインスタンスにk8s-nvidia-pluginを導入しCUDAを有効化

現在p2.xlargeインスタンスでCPU推論を動かしていて、8m/imgの性能しか出ていない...。
UXを考えると、遅くとも5m/imgを目指したい。

8m/imgはあくまでリクエストキューが1枚しかない場合の数値。現状キュー数に応じてのオートスケールができていないため、キュー数に比例して解析時間も長くなる。GPU対応の後はアーキテクチャ的な速度改善も入れる。

cuda有効かしてpytorchでGPU認識できているにも関わらず早くならないなぁ...。

AIエンジンのpytorch側からGPUを認識できているにも関わらず、いくらGPUインスタンススペックあげても画像一枚の処理時間がほとんど変わらないなぁ...。CPUスペックも付随してアップしてるはずなんで、どうしてもこの値が処理性能限界か...? そんなことある?

p2.xlarge: 8m/img
p2.8xlarge: 8m/img
p2.16xlarge: 8m/img
g4dn.xlarge: 8m/img
g4dn.8xlarge: 6m/img
g4dn.16xlarge: 7m/img

もう少しエンジンの実装レベルも詰めてみて、それでもダメならアーキテクチャ面の速度改善に移る。

あとp3インスタンスも試してみよう

nvidia-smiでGPU使用率見るの忘れてた...

p3.xlargeだと10m掛かった。nvidia-smiでGPUメモリ使用率確認すると、平均:1%, 最大: 40%。
逆に遅くなったのは謎...。

おっとCPU使用率100%に張り付いてるぞ、これか

メモリは20%程度で止まってる。CPUバウンドだった?

CPU強強インスタンスを試してみよう

CPU最適化インスタンスのc5.metalで試すと7m/img

  • GPUの有無は処理速度に関係ない?
  • CPU性能格段に上がったのに処理速度はほぼ変わらず
    • CPU使用率はずっと100%以上

もうよく分からなくなってきた

ログインするとコメントできます