Mitsunobu Homma
Dec 10, 2018 · 11 min read

ミクシィ Advent Calendar 2018 10日目の記事です。

こんにちは。ミクシィ x 18新卒 x CRE = @mitsu9 です。

CREではTerraformを用いてAWSの各種リソースを管理しています。
また、最近は機械学習を取り入れており、そのデプロイにはAWSのSageMakerを利用しています。
となるとTerraformでSageMakerの管理をするのが筋ですが、意外に躓いたところがあったので書いていこうと思います。

SageMakerについて

SageMakerはAWSのフルマネージドな機械学習のサービスです。
Build・Train・Deployの3つのモジュールがあり、全てをAWSで完結させることも一部のモジュールのみを利用することもできます。

今回はその中でもDeployに焦点を当てます。
SageMakerで推論用インスタンスを立てる場合に必要な前準備は以下の2つだけです。

  • 推論の際に必要なデータ(学習済みモデルなど)をS3に置く
  • 推論用インスタンスで利用するDocker imageをECRに置く

ここではこの準備は終わっている前提で進めていくのでこれらについては特に書きません。
(S3とECRでもいろいろ躓いたので別記事でまとめる予定です。)

推論用インスタンスを立てるために設定が必要な項目はModel・EndpointConfig・Endpointの3つです。
ModelはどのS3のデータをロードするのかと、どのECRのイメージを利用するのかを保持します。
EndpointConfigはどのModelを利用するかを保持します。SageMakerでは複数のモデルをA/Bテストできたりもするので、複数のModelを参照することもあります。また、推論用インスタンスのインスタンスタイプもこちらで設定します。
Endpointは推論用インスタンスそのものであり、どのEndpointConfigを用いて起動されたインスタンスなのかを保持しています。

ここまでの説明を図に整理すると以下のようになります。

そして、Terraformを用いてSageMakerで推論用インスタンスを立てようと思った時、これらのリソースを管理すれば良いということになります。

SageMakerを管理するためのプラグインを入れる

12月10日時点では本家のterraform-provider-awsにSageMakerを管理する機能は入っていません。
そこで、現在開発中のproviderをプラグインとして導入する必要があります。
プラグインの導入は非常に簡単で、cloneしてbuildして指定のディレクトリに置いておくだけでOKです。
注意点としては、指定のディレクトリがTerraformを実行する環境によって変わることと、公式ドキュメントにはそのことが書かれていないことです。

必要なIAMを作る

それではここからはTerraformのコードを書いていきます。
まずはじめに必要となるIAMを追加します。

SageMakerを利用する場合、Modelに対してIAM roleを割り当てる必要があります。
ModelはS3とECRを参照するため、このroleにはSageMaker・S3・ECRへのアクセス権限を与えておけば問題ありません。

とりあえずTerraformでSageMakerの推論用インスタンスを立ち上げる

必要な準備は終わったのでいよいよSageMakerを管理するコードを書いていきます。
前述の通り管理する対象はModel・EndpointConfig・Endpointの3つです。
とりあえず立ち上げるだけであれば特に何も無いので、ここではコードを貼ってさっと次にいきます。

推論用インスタンスのモデルを更新する

とりあえず立ち上げるだけであれば何も問題は無いのですが、運用するとなるといろいろ問題が発生してきます。
その中の1つが、より精度の良いモデルができた時に新しいモデルに切り替えることです。

せっかくTerraformを使っているので、必要な箇所だけをサクッと変更してterraform applyすることでエンドポイントが更新されるようにしたいですよね。
具体的には、「参照するモデル(=Modelが持つS3のパス)だけを変更すればEndpointが更新されるようにしたい」です。
これが意外と難しく時間がかかってしまったので、どうすれば実現できるのかを説明したいと思います。

結論としては、

  • ModelだけでなくEndpointConfigも変更されるようにする
    =ModelとEndpointConfigのnameにuuidを含め、ignore_changesを使う
  • 更新処理の実行順を制御する
    =create_before_destroyを使う

の2点だけでTerraformを使ってダウンタイム無しにモデルを切り替えることができます。

それでは1つずつ説明していきます。

ModelだけでなくEndpointConfigも変更されるようにする

「参照するモデル(=Modelが持つS3のパス)だけを変更すればEndpointが更新されるようにしたい」ので、まずはModelのmodel_data_urlだけを変更して実行してみます。
この場合、Terraformでは差分のある箇所だけに変更が適用されるため、Modelは更新されますがEndpointは更新されません。
Endpointの更新処理を実行させるためにはEndpointの何かが変わることが必要です。
そこで、endpoint_config_nameを変更する、すなわちEndpointConfigのnameを変更することでEndpointの更新処理を実行させるというアプローチを考えます。

しかし、今回やりたいことは「参照するモデル(=Modelが持つS3のパス)だけを変更すればEndpointが更新されるようにする」ということです。
新しいモデルをデプロイするたびにEndpointのnameを変更することは本望ではありません。
このような際にまず思いつくことはtimestampやuuidを利用することで実行毎に値を変えることかと思います。
この方法の良い点はコード上は変更が無いためPRには表示されないが、実際の値は実行毎に変わるためTerraformが適用されるということです。
一方で毎回値が変わるため、SageMakerに全く関係ないリソースを変更した時にも更新処理が実行されてしまいます。
そこで、ignore_changesを使うことでうまく更新のタイミングを制御することができます。
ignore_changesは名前の通り変更があっても無視するというもので、ここで指定した変数の変更では更新処理が実行されることはありません。

ここまで説明した箇所だけに絞ったコードが以下のものになります。

このコードは何回実行しても最初の一回しか実行されません。
nameはuuidを含むため毎回違う値になりますが、ignore_changesで指定しているためこの差分は無視されます。
それ以外の箇所は変更しない限り勝手に値が変わることは無いため、このコードは最初の一回しか実行されません。

それでは「参照するモデル(=Modelが持つS3のパス)だけを変更すればEndpointが更新されるようにする」という目標が実現されそうかを確認していきます。
model_data_urlを変更すると以下のように処理が行われます。

  1. model_data_urlが変更されたのでModelの更新処理(正確には今のModelの削除と新しいModelの作成)が実行される
  2. Model.nameが変更されるため、EndpointConfigのmodel_nameが変更され、EndpointConfigの更新処理が実行される
  3. EndpointConfig.nameが変更されるため、Endpointのendpoint_config_nameが変更され、Endpointの更新処理が実行される

良さそうですね!
これでmodel_data_urlを変更するだけでEndpointの更新処理が実行されるようになりました!

更新処理の実行順を制御する

ここまででmodel_data_urlの変更のみでEndpointの更新処理が実行されるようにしましたが、実はこれだけではうまくいきません。

現在のままでは更新処理は以下のような順序で実行されます。

  1. sagemaker-endpoint-config: Destroying
  2. sagemaker-model: Destroying
  3. sagemaker-model: Creating
  4. sagemaker-endpoint-config: Creating
  5. sagemaker-endpoint: Modifying

この順番で処理がされた場合、最後のEndpointの更新処理の際に次のようなエラーで落ちてしまいます。

* aws_sagemaker_endpoint.sagemaker-endpoint: ValidationException: Could not find endpoint configuration

このエラーはEndpointの更新が行われる際に現在のEndpointConfigが既に削除されているため、EndpointConfigが見つからないというものです。
このエラーを回避するためには、以下のような順番で処理が実行される必要があります。

  1. sagemaker-model: Creating
  2. sagemaker-endpoint-config: Creating
  3. sagemaker-endpoint: Modifying
  4. sagemaker-endpoint-config: Destroying
  5. sagemaker-model: Destroying

今回のようにTerraformで実行順序を制御するためにはlifecycleのcreate_before_destroyを利用します。
create_before_destroyを指定すると名前の通り作成をしてから削除をおこないます。

lifecycleとは変更があった際の挙動を指示するためのもので、create_before_destroyの他にも先ほど使ったignore_changesとリソースの削除を防ぐprevent_destroyがあります。
Terraformの細かい挙動を設定したい時にlifecycleは便利かと思います。

これでようやく「参照するモデル(=Modelが持つS3のパス)だけを変更すればEndpointが更新されるようにする」という目標を達成することができました。
以上のことをまとめた最終的なコードがこちらです。

最後に

本当はCREいいぞ!楽しいぞ!という記事を書こうと思っていたのですが、気がつけば普通の技術記事になってました。
CREの話はまたどこかで書ければと思ってます。

明日はyaiwaseさんです!

mixi developers

ミクシィグループのエンジニアやデザイナーによるブログです。

Mitsunobu Homma

Written by

Customer Reliability Engineer at XFLAG Studio.

mixi developers

ミクシィグループのエンジニアやデザイナーによるブログです。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade