AWS CloudShell を Serverless Framework × Python のデプロイ環境として使ってみた

記事タイトルとURLをコピーする

AWS CloudShell がリリースされ、ブラウザ上でシェル環境が使えるようになりました。

aws.amazon.com

このアップデートによって、「ローカルを汚さないCLI環境」が簡単に手に入るようになりました。

GCP では以前から "Cloud Shell" という名称で存在していたサービスですね。個人的な検証でたまに GCP の Cloud Shell を触ることがあり、ちょっとした作業や検証をするには便利だなと思っていました。なので、今回 AWS が追随して AWS CloudShell をローンチしたことにとてもテンションが上がっています。

紹介記事はすでに弊社の保田が記事(下記)を出していますので、私の方では実作業に使ってみての所感などシェアしようと思います。

blog.serverworks.co.jp

前置き

社内SEである私の業務的には、本番デプロイの作業が CloudShell を使うモチベーションの1つになります。 自分のローカルマシンに本番アカウントのアクセスキーを残したくはありませんよね。ブラウザからサクっと入って作業環境が手に入るのであれば、それが一番てっとり早いです。

ちょうどよく Serverless Framework を用いた Bot アプリケーションのリプレス作業があり、せっかくだからデプロイ作業に CloudShell を使ってみようという運びになりました。

デプロイ対象のアプリはAPI Gateway の裏に Lambda Function が1本あるだけのごくシンプルな構成でした。言語は Python を利用しています。

経緯を補足しておくと、もともとデプロイ対象の Bot はデプロイツールを一切使っておらず、手作業で zip を作ってマネジメントコンソールからアップロードするという最も原始的な方法でデプロイをしていました。長らく塩漬けだったのですが、このたびこの Bot の動作に問題が発覚し、多少の構成変更をする必要が出ました。で、ついでに今の原始的デプロイを脱却し、Serverless Framework に乗せてしまおうと相成りました。

※「本番デプロイであれば、そもそも手作業でやることが間違いでは?」「パイプラインに乗せて自動化すべきでは?」という指摘があると思います。これはごもっともですので甘んじて受け止めます。 ただ、昔から塩漬け保守しているコードもひっくるめると必ずしも「あるべき姿」ではない環境もあり、本番アカウントの権限でCLIを叩ける環境が欲しいケースも出てきます。今回のはちょうどそういう案件でした

使用感

タイプしてみた感じは至って良好でした。

IAM はその時マネジメントコンソールにログインしていたときのものを引き継ぐようで、何も考えずにスッと利用開始できるので非常にシンプルです。 ラグ感もほぼ感じませんでした。20分アイドル状態だとタイムアウトするようですが、今回は基本的にずっとシェル上での作業だったので特に困ることもありませんでした。

少し残念なのは履歴からの補完が有効になっていないことですね。自分で bashrc に

bind '"\C-n": history-search-forward'
bind '"\C-p": history-search-backward'

などのように記述することで有効化する必要があります。後述しますが、 ホームディレクトリに書き込んだ内容は永続化されるので、CloudShell のセッションが切れても問題なく次回以降使い回すことができます。bashrc や vimrc など、自分好みの設定を一度入れてしまえばOKなのはありがたいですね。

標準のログインシェルではないものの、 zsh もインストールされています。zsh だと oh-my-zsh のようなプラグインが有名かと思いますが、そのへんもホームディレクトリ以下にインストールしておけば手元に近い環境が再現できそうです。

また、個人的には CLI ベースのハンズオンをやるときに重宝しそうな気がします。ハンズオンの主催側としては参加者のマシン環境の差異を考えずに済むので非常に頼もしい存在になりそうだと感じました。

今回の作業はラフにデプロイしていいアプリだったので非常に気楽に試せました。しかしデプロイが成功するまでに多少は詰まった点もあるので、そのへんの話を以下で続けます。

デプロイ時に気をつけること

「Serverless Framework で Python ベースの lambda をデプロイする場合」を前提とした留意事項として見ていただけたらと思います。

※本記事において、デプロイの作業手順そのものを示すことにはあまり意味がないと考えますので、端折ります。

まず、実行環境を見ていきます。

実行環境に関連する話

CloudShell は Amazon Linux 2 ベースのコンテナ環境です。

セッションを開始すると、 /home/cloudshell-user をカレントディレクトリとした bash が起動します。 uname を実行すると、Amazon Linux 2 ベースであることが確認できます。

# uname -a
Linux ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal 4.14.209-160.335.amzn2.x86_64 #1 SMP Wed Dec 2 23:31:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

パーティションも見てみましょう。df コマンドを叩いてみたところ、以下のような結果が返りました。

# [cloudshell-user@ip-xxx-xxx-xxx-xxx ~]$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          30G   12G   17G  41% /
tmpfs            64M     0   64M   0% /dev
shm             1.9G     0  1.9G   0% /dev/shm
tmpfs           1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/nvme1n1     30G   12G   17G  41% /aws/mde
/dev/loop0      976M  2.6M  907M   1% /home
tmpfs           1.9G     0  1.9G   0% /proc/acpi
tmpfs           1.9G     0  1.9G   0% /sys/firmware

※実行する環境や時期によって細かい数字は変わる可能性があるので、数字は参考程度に。

ルートファイルシステムが "overlay" になっていることから、コンテナベースであることは察しがつく方もいらっしゃると思います。ssh が可能な Lambda あるいは ECS のようなもの...と思えばよいでしょう。

パーティション

ここで特筆するのは /home とルートパーティションです。

/home に保存したファイルは、 CloudShell のセッションを閉じても残ります。作業環境として共通で残しておきたいモノがあればここに置いておくのが良いでしょう。しかし、全体で約 1GB とあまり容量は大きくありません。

ルートパーティション(正確には /home 以外)はエフェメラル領域です。このパーティション配下に置いたファイルはセッションが切れると同時に削除され、永続化されません(自発的に CloudShell を再起動した場合も削除されます)。しかし、初期状態で 17GB 程度空きがあります。ユーザーの立場としては、用事が終わったら消えていい一時的な作業ファイルの置き場所として割り切るなら使えそうです。

/home の空き領域問題

先ほどの df の実行結果を見ると、 /home のパーティションで使える容量は 1GB 弱ほどしかないことがわかります。この容量は非常に心もとなく、 Serverless Framework を使ったプロジェクトの場合、パッケージインストールやデプロイのタイミングで容量不足になる可能性が非常に高いです。

まず Serverless Framework 本体やそのプラグインで数百MBほどの容量が必要ですし、Python の実行環境ならランタイムバージョンを切り替える仕組みとして pyenv を使いつつ複数バージョンの処理系を入れることが普通でしょう。

※2020.12.17 現在の CloudShell では、 Python 3 系は 3.6 と 3.7 がインストールされています。よって、3.8 が欲しいなら追加インストールが必要です

加えて、デプロイ時にデプロイパッケージの作成処理があります。Serverless Framework はプロジェクトディレクトリ直下の .serverless/ 以下に依存関係をコピーして zip 化し、それをS3にアップロードします。ここでもまた容量を使ってしまいます。 /home で作業する限りは、どこかでほぼ必ず Disk full になってしまうと思います。

この問題の解消方法ですが、作業環境として共通で入っていてほしいものは /home の下に入れる、それ以外の、デプロイ対象に関する作業は /tmp の下で実施する のが良いと思います。

今回のコードベースで言えは、 /home の下に置くべきものは pyenv と pyenv 経由でインストールした Python の処理系のみです。pyenv + 任意のPython 処理系を入れる作業はほぼどのプロジェクトでも発生すると思われます。CloudShell をデプロイ作業の環境とみなすならば、少なくとも Python の実行環境周りに関しては /home に置いておきたい気がします。

clone するソースコードや Serverless Framework 関係はすべて /tmp (ルートパーティション)の下に配置して作業します。デプロイ作業が目的ですので、セッションが切れてファイルが消える分には問題ないはずです。Serverless Framework 関連の依存は基本的にプロジェクト単位でバージョン等も異なるでしょうし、/home に入れておく必要はないでしょう。プロジェクトごとに npm install して使えば問題はないはずです。

Serverless Framework パッケージング処理の実行環境差異

ここでお話しすることは Serverless Framework のデプロイ事情にも大いに関係します。なので半分以上は CloudShell に関係ない話です。ご了承ください。CI/CD の実行環境でも同じような話はあるので、既存っちゃ既存の問題と言えます。

おおむね、以下の前提条件に当てはまる方はこの節に目を通しておくと良いことあるかもしれません。

  • numpy など、 Pure-Python ではないパッケージをデプロイに同梱する必要がある
  • ローカルの開発環境に Amazon Linix 以外のOSを利用している
  • Serverless Python Requirements を利用している

プラットフォーム依存な機能を利用する Python パッケージを扱う場合(たとえば cryptography など)、ローカルマシンの環境でパッケージングしたものをデプロイしても Lambda の実環境では動作しません。この問題を解消するには、Amazon Linux ベースの docker を起動し、パッケージング処理だけはその中で行うようにする方法が一般的です。Serverless Python Requirements はパッケージングを docker 環境で行うための設定方法を提供しており、 serverless.yml に以下のような記述を行うことでパッケージングを docker 内で実行できます(公式ドキュメント Serverless Python Requirements "Cross compiling" セクションも参照ください)。

custom:
  pythonRequirements:
    dockerizePip: true

Mac や Windows のローカルマシンでパッケージングする分にはこれでOKです。しかし、Amazon Linux 2 ベースである CloudShell 環境ではこの記述は不要です。そのため、CloudShell 環境でデプロイ作業をやるなら、clone した後 serverless.yml を修正して dockerizePip オプションを無効化するのが(その場的には)一番楽です。

...。とはいえ、できればいい感じに dockernizedPip をスイッチングできるようにしたいですね。clone したソースを手で修正するなんてナンセンスです。(CodeBuild などの CI 環境のコンテナでもこれと全く同じ問題は発生しますし)

私自身しっくり来るやり方は見つけていないですが、安直かつ手軽にいくなら dockernizedPip の値をオプションや環境変数あたりから引っ張るのが楽です。

# serverless.yml
custom:
  pythonRequirements:
    dockerizePip: ${strToBool(${opt:dockernizePip, "true"})}
# deployment

# at CloudShell
npx sls deploy --stage dev --dockernizePip false

# at local machine
npx sls deploy --stage dev --dockernizePip true

CloudShell や CodeBuild など Amazon Linux ベースの環境でデプロイコマンドを叩く場合は、環境変数(またはオプション)で dockernize の無効化を明示すればOKです。これで同じコードベースをそのまま使いまわせますね。パッケージングの実行環境差異に起因する問題はこれで対応可能です。

おわり

本番アカウントの IAM ユーザーをローカルの CLI に設定する必要がないのは嬉しいですね。デプロイ作業自体は順調とはいきませんでしたが、代わりに CloudShell の使用感が多少わかったのでよしとします。

今回のユースケースにおいて、CloudShell 自体の仕様について気になったのはディスクの容量問題くらいです。でも、おそらくこれは時間が解決する問題のような気もします。そのうちしれっとストレージが拡張されたり EFS マウントがサポートされたりするんじゃないでしょうか。

ラグ感もほぼほぼなかったので、作業環境としての EC2 インスタンスは今後 CloudShell に置き換わっていくかもしれません。とはいえ、がっつりした運用作業に向く用途ではなさそうとも感じますので、当面はラフに検証/作業したいニーズがあり、かつその進め方が(社内ポリシー的に)許されるTPOの場合でのみ使ってみることになりそうですね。

最後に。re:Invent の日程もそろそろ終了となりますが、動画や資料のアーカイブが見られるセッションもありますので未登録の方はぜひ(宣伝)

reinvent.awsevents.com

橋本 拓弥(記事一覧)

プロセスエンジニアリング部

業務改善ネタ多め & 開発寄りなコーポレートエンジニアです。普段は Serverless Framework, Python, Salesforce あたりと遊んでいます