DEVGRU

プログラミングと競馬予想について書きます

Amplify CLI を使わずに AWS CLI だけで Amplify Hosting にデプロイする

あらまし

GitHub ActionsGatsby.js で生成したサイトを AWS Amplify にデプロイしたいが、 Amplify CLI のインストールをしなくともデプロイする方法が見つかったので紹介する。

広告

AWS Amplify (以下 Amplify)は、AWSのホスティングフレームワークで1つのアプリに対してフロントエンド・バックエンドそれぞれをデプロイできる。 フロントエンドはブランチごとのデプロイが可能で、本番・開発・作業ブランチなど複数のデプロイをすることが可能で、本番だけ独自ドメインを割り当ているというのも可能だ。

Amplify CLI という Node.js ベースのコマンドが用意されているが、これは比較的バックエンド寄りの機能が多くデプロイのみであればこれがなくても十分だ。

本来は GitHub と連携して Amplify の CI でビルド・デプロイをするが、諸般の事情(主に速度)により、GitHub Actions からデプロイしたくなった。 GitHub Actions には AWS CLI がデフォルトでインストールされているので、これを用いる。

手順

手順は以下の通り

準備

  1. AWS CLI からアプリを作成し、 appId を取得する

デプロイ

  1. ブランチを作成
  2. デプロイを作成
  3. デプロイの zipUploadUrl にzip圧縮したコンテンツをPUT
  4. デプロイを開始
  5. ジョブの終了を待つ

準備

この手順は一度きりしか行わないので、AWSコンソールからしても良いが、その場合はなにか初回デプロイ用のファイルを用意しないと先へ進めなかった。

AWS CLI からは以下の手順でアプリを作成する。

aws amplify create-app --name my-app | tee app.json

戻り値の中に appId があるため、これを保存しておく。

デプロイ

まずはアプリの中にブランチを作成する。

aws amplify create-branch --app-id appId --branch-name my-branch-name

Amplify ではブランチごとにデプロイされURLが割り当てられ、ブランチに対してデプロイを行うとURLの内容が更新される仕組みになっている。 本番用、開発用などの用途で分けて作ると良い。

GitHub Actions の場合、PR が立っている Git ブランチへの更新でトリガーしてデプロイするとデプロイ、といった作りにするが、 その場合は、以下のように「なければ作る」ようにすれば良い。

aws amplify get-branch --app-id appId --branch-name my-branch-name || aws amplify create-branch --app-id appId --branch-name my-branch-name

次に、ブランチに対してデプロイを作成する。

aws amplify create-deployment --app-id appId --branch-name my-branch-name | tee deployment.json

デプロイを作成すると Zip ファイルをアップロードするためのURLが zipUploadUrl に、デプロイを実行するジョブの状況を取得するためのジョブ ID が jobId にそれぞれ返されるので、それを保存する。

今回は Gatsby.js だが、React でも Vue でも Angular でも、静的ウェブページと呼ばれるHTML/JS/CSS/画像の塊なら Amplify にデプロイ可能なので、それぞ Zip ファイルに固めておく(ここでは artifacts.zip としておく)。

zipUploadUrl に対して zip ファイルを PUT で送信する。

curl -X PUT zipUploadUrl --data-binary @artifacts.zip

デプロイの準備が完了したので、デプロイを開始する。

aws amplify start-deployment  --app-id appId --branch-name my-branch-name --job-id jobId

一定時間待つ必要はあるが、デプロイはさほど時間も立たずに終わる。 ジョブが終了したのを確認できれば完了となる。

while [[ "$(aws amplify get-job --app-id appId --branch-name my-branch-name --job-id jobId | jq -r '.job.summary.status')" =~ ^(PENDING|RUNNING)$ ]]; do sleep 1; done

デプロイしたあとのURLは、アプリの defaultDomain とブランチの displayName を組み合わせた URL になる。

現在は、以下のようなスクリプトを使用してデプロイを行っている。

APP_ID=app-id # aws amplify create-app で取得
BRANCH=branch-name

aws amplify get-branch --app-id $APP_ID --branch-name $BRANCH || aws amplify create-branch --app-id $APP_ID --branch-name $BRANCH
aws amplify create-deployment --app-id $APP_ID --branch-name $BRANCH > deployment.json
JOB_ID=$(jq -r .jobId < deployment.json)
ZIP_UPLOAD_URL=$(jq -r .zipUploadUrl < deployment.json)
curl -X PUT $ZIP_UPLOAD_URL --data-binary @artifacts.zip
aws amplify start-deployment --app-id $APP_ID --branch-name $BRANCH --job-id $JOB_ID
while [[ "$(aws amplify get-job --app-id $APP_ID --branch-name $BRANCH --job-id $JOB_ID | jq -r '.job.summary.status')" =~ ^(PENDING|RUNNING)$ ]]; do sleep 1; done

BRANCH_DISPLAY_NAME=$(aws amplify get-branch --app-id $APP_ID --branch-name $BRANCH | jq -r .branch.displayName)
DEFAULT_DOMAIN=$(aws amplify get-app --app-id $APP_ID  | jq -r .app.defaultDomain)
URL="https://${BRANCH_DISPLAY_NAME}.${DEFAULT_DOMAIN}/"
echo "URL: $URL"

また、PRをクローズした際にはブランチを削除する必要があるため、以下のコマンドで削除している。

aws amplify get-branch --app-id $APP_ID --branch-name $BRANCH

なお、上記を実行する際は以下の IAM Policy が必要となるので、CIで実行する際は予め用意しておく必要がある。

  • amplify:CreateBranch
  • amplify:CreateDeployment
  • amplify:DeleteBranch
  • amplify:GetApp
  • amplify:GetBranch
  • amplify:GetJob
  • amplify:StartDeployment

参考: - https://docs.aws.amazon.com/ja_jp/cli/latest/reference/amplify/index.html#cli-aws-amplify

エンジニア&エンジニアになりたい人におすすめの会社テックブログ

エンジニア&エンジニアになりたい人におすすめする会社テックブログ一覧です(2022年1月29日更新)。


広告


株式会社テラスカイ のテックブログです。

Salesforce ネタが多めです。


株式会社スマートスタイル のテックブログです。

DB ネタが多めです。


クラスメソッド株式会社 のテックブログです。

クラウド、主にAWS系を中心に幅広い記事があります。


Retty株式会社 のテックブログです。

クラウド、データ分析、モバイルアプリの他、開発組織についての記事もあります。


株式会社メルカリ のテックブログです。

イベント開催報告が積極的に行われています。


株式会社リクルート のテックブログです。

複数あるリクルートのTECHBLOGの記事一覧です。


フューチャー株式会社 のテックブログです。

クラウドやIoTの他、登壇記事があります。


株式会社 エム・フィールド のテックブログです。

クラウド、モバイル他、組織やEC分野のトレンドなどの記事があります。


株式会社エウレカ のテックブログです。

AWS, Goの記事多めです。


KCCSモバイルエンジニアリング株式会社 のテックブログです。

Tableau の記事が多いです。

以上です。

neologdn が使えない (Python 3.8では)ので unicodedata.normalize() を使ったが、やっぱり使えた話

データの名寄せに必要な正規化で同僚から neologdn を進められて使おうとしたが、 Python 3.8 に対応していなくて、 unicodedata.normalize() で事足りたけど、3日前に対応していたお話です。

広告

ユーザの入力値とデータベースを照合して最もマッチする値を取得する実装が必要になり、同僚が neologdn ( PyPI) を教えてくれたので、さっそうと pip install neologdn したところ、見事に使えなかった。

$ pip install neologdn
...(なにか大量のコンパイルエラー)

検索して出てくる記事や、 PyPIの配布物 を見たところ、Python 3.7 までのビルド済みパッケージしかなく、 Python 3.8 でビルドが走るがエラーになる、といった具合のようだった。

ユーザの入力値はせいぜいカタカナひらがな数字の半角全角程度の変換だったので、unicodedata.normalize() で済ませた。

>>> unicodedata.normalize("NFKC", "あいうえお")
'あいうえお'
>>> unicodedata.normalize("NFKC", "アイウエオ")
'アイウエオ'
>>> unicodedata.normalize("NFKC", "12345.6")
'12345.6'
>>> unicodedata.normalize("NFKC", "㈱㈲")
'(株)(有)'

あとはレーベンシュタイン距離でデータベースに入っている値との類似度で並べ替えることで、無事タスクを完了することができた。

from unicodedata import normalize
from Levenshtein import distance

onigiri_db = ["ウメボシ", "タラコ", "オカカ"]


def lookup_onigiri(input_):
    with_similarities = [
        (
            onigiri,
            distance(onigiri, normalize("NFKC", input_))
            / max(len(onigiri), len(input_)),
        )
        for onigiri in onigiri_db
    ]

    for onigiri, similarity in sorted(with_similarities, key=lambda elem: elem[1]):
        print(f"{onigiri}\t{similarity:.4}")


if __name__ == "__main__":
    import sys

    if len(sys.argv) >= 2:
        lookup_onigiri(sys.argv[1])
    else:
        print(
            f"""
Usage:
    {sys.argv[0]} おにぎりの具
""".lstrip()
        )
$ python similarity.py ウメ
ウメボシ    0.5
タラコ   1.0
オカカ   1.0

そしてこの記事に顛末を書いている最中、 pip install neologdn のコンパイルエラーをもう一度拝もうとしたところ、インストールが無事終了してしまった。

pip install neologdn
Collecting neologdn
  Using cached https://files.pythonhosted.org/packages/12/46/0bb6c64ff8b9c549a3fbdff68240155fb5f938a2563ce5396278973919f0/neologdn-0.5.1.tar.gz
Installing collected packages: neologdn
  Running setup.py install for neologdn ... done
Successfully installed neologdn-0.5.1

はて…? と思って PyPI を見たところ、なんと3日前に更新されていたようだ。しかも2年と少しぶりに。

PyPI のスクリーンショット
https://pypi.org/project/neologdn/#history のスクリーンショット(2021/05/05)

GitHub のスクリーンショット (2021/05/05)
GitHub のスクリーンショット (2021/05/05)

タスクが 2021/05/02 の日中で、PyPIの更新日時が "2021-05-02 20:44:14" 1 なので、半日ほどのすれ違いだったようだ。なんと…。

自身のタスクには間に合わなかったが、同僚が別のタスクで使用する予定だったので非常にタイミングが良かった。

Pythonで動かして学ぶ 自然言語処理入門

Pythonで動かして学ぶ 自然言語処理入門

現場で使える!Python自然言語処理入門

現場で使える!Python自然言語処理入門


  1. スクリーンショットで示した PyPI の日付にマウスオーバーすると表示される