ポートフォリオのデプロイフローを修正した

Posted on 2020-01-27 in zakki

以前作成したポートフォリオは S3 にデプロイしています。ソース管理を CodeCommit でビルドとデプロイを CodeBuild で行っていたのですが、いつのタイミングからか CodeBuild のデプロイ部分が失敗するようになってしまいましたので、原因の確認と対処を行いました。

事象

AWS CodeCommit - AWS CodeBuild で Pipeline を組んでいて、CodeBuild の buildspec.yml で generate したあと S3 バケットを対象に aws s3 sync で同期を行い更新を行っていましたが、いつの頃からかエラーが発生しだしビルド失敗ステータスとなってしまいました。同時期に S3 バケットに設定していたバケットポリシーが怪しいことに目星はつけていましたが ( ポリシーを外すとsyncが正常に完了するので )、原因は分からず少しの間放置してしまっていました。しかしながら、いつまでもこの状態はさすがに … と思い直し原因調査を行いました。

原因解析

エラー

そもそもどうやってエラーを確認しよう、と考えていましたが単純にバケットのアクセスログを取ろうと思い、sync先となるバケットのアクセスログを収集し確認したところ、以下のようなエラーログが発生していました。本当は色々と長かった表示なのですが情報がアレなところもありちょうどいいように整形しています。

6412( 略 ) paraselene92.io 
[25/Jan/2020:06:20:12 +0000] 10.0.72.123 arn:aws:sts::
( 略 )
REST.PUT.OBJECT favicon.ico "PUT /paraselene92.io/favicon.ic
o HTTP/1.1" 403 AccessDenied 243 3094 3 - "-" "aws-cli/1.16.242 Python/3.6.8 Li
ux/4.14.152-98.182.amzn1.x86_64 exec-env/AWS_ECS_EC2 
( 略 )

いや10.0.72.123て。

原因

CodeBuild が生成するビルド用コンテナの IP アドレスはプライベートアドレスなのですね。CodeBuild が S3 にアクセスしデプロイを行う設計でしたので、S3 のバケットポリシーには CodeBuild が使用するとされている IP アドレスレンジを許可設定していたわけですが、どうやらこのコンテナの IP アドレスレンジとは異なるもののようです。( じゃあどこで使うんだあのレンジ …)

バケットポリシーにプライベート IP のアドレスレンジを許可するわけも無いので、ここで弾かれていたのですね。

対処

対処案の検討

可能であれば小さい修正で済ませたかったので CodePipeline の構成を変えずにどうにかならんのか、と CodeBuild からの延長線上で何とかできないかと検討してみました。

  • CodeBuild を VPC に参加させる
    • VPC の S3 エンドポイントから S3 バケットにアクセスさせる
    • NAT ゲートウェイ ( インスタンス ) を介して外部から S3 にアクセスさせる
    • プロキシサーバを利用して …( 本当?)

少し確認してみると CodeBuild のオプションで VPC( サブネット ) に所属する項目があり、それを利用する案がありそうです。しかしながら、VPC に所属したあとの S3 へのアクセス手段の用意が、管理面や費用面からうーん … と思い始めてしまい、ちょっとこれは … とストップ。公式がプロキシサーバとか挙げててびっくりしてしまった。

パイプライン自体を更新するのが、費用的にも時間的にも管理的にも早いのではと思い直し、CodePipeline にデプロイ機構を組み込む方針に切り替えました。CodePipeline のデプロイステージから直接 S3 バケットへのデプロイが可能になったのが約 1 年前とのこと。

対処案

以下のように考えました。Deploy Stageのよいアイコンが無かったので、都合上 CodePipeline のアイコンと同じもの。

  • パイプラインにデプロイ機構を組み込む
    • CodePipeline にデプロイプロバイダを S3 としたデプロイステージを組み込む

出力アーティファクトと構成のずれ

さて、いざパイプラインの一連の動作を見ていると、

  • CodeBuild の出力アーティファクトは CodeBuild で出力設定した S3 バケットではなく、CodePipeline で設定しているアーティファクトストアに基づいて出力されている。
    • パイプライン名の階層配下に SourceArtiBuildArtif のフォルダが作られアーティファクトが格納される。
    • CodeBuild 単体で動作させるとアーティファクトは CodeBuild で出力設定した S3 バケットに格納される。
  • CodeBuild の出力アーティファクト自体は圧縮ファイル
  • CodePipeline がデプロイするものは圧縮ファイルを解凍したもの

という事が分かった。1 番目の点でまず混乱させられた。これは基本の動作なのだろうか。余裕があったら調査しておきたい。

肝は 3 番目で、解凍したものを静的ホスティングしている S3 バケットにデプロイすればよかったのだけど、解凍したファイルは一つのフォルダにサイトを構成するファイル等が纏まっていてしまい、少し思っていた構成と異なっていた。このままでも運用出来るかもしれないが、S3 バケットに格納したファイルの構成変更のみ手作業というのものアレなのでもう少し考える。

再度 s3 sync に向けて

構成変更を行う方法として、CodePipeline がデプロイしたバケットについてaws s3 syncを行い、静的ホスティングしている S3 バケットにsyncできないかどうかを考えた。下記のイメージ。

  • CodePipeline がデプロイしたバケット : Deploy Bucket
  • 静的ホスティングしている S3 バケット : Prd Bucket

実際のsyncは、Deploy Bucketにデプロイしたことをトリガに Lambda を動作させればよいかな、と考えましたがaws s3 syncAWS CLIコマンドのためそのままでは Lambda で使用できないようです。Lambda 用にboto3で書くことを考えましたが、いい機会と思い Custom Runtime を使用してみました。

Custom Runtime の作成

一昨年 ( になるのか …) に発表されたCustom Runtimeです。bootstrap というファイルを用意することで、任意 ( に近い ) プログラミング言語で Lambda が使用できます。少し検索してみると接しやすい題材なのかAWS CLIを使うbashでの使用例も数多くありましたのでそちらを参考に進めます。

( 参考 ) カスタムランタイムを使って Lambda で AWSCLI を動かす

  • bootstrap (layer)
#!/bin/sh

set -euo pipefail
set -x

export HOME=/tmp
pushd /tmp
export PATH=/tmp/.local/bin:$PATH

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Execute the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done
  • function.sh
function handler () {
  RESPONSE=$(aws s3 sync s3://(デプロイ先のバケットパス)/ s3://(静的ホスティングのバケットパス)/)
  echo $RESPONSE
}

function.sh が最低限過ぎるのでもう少し手を加えてもよいかも。

最終構成

以下のようになりました。改めて、CodeCommit へコミットを行うことで静的ホスティングを設定しているバケットへのデプロイが可能になりました。

他にやりたいこと

デプロイフローは修正できたので、次にやっておきたいことです。

  • プルリク運用をイメージしたデプロイフローにする

サイトの性質上、1 名 ( 私 ) しか触れないリポジトリのため Master ブランチのみの運用でも支障があまり無いのですが、それもいかがなものだろう … と思うのでプルリクや複数ブランチ運用に対応できるデプロイフローにしたい。

  • 各ステージの通知有効化

コミット、ビルド、デプロイの各工程の結果通知の有効化。これは実装するだけであれば早いのかな、と勝手に思っている。

  • Custom Runtime の結果通知の実装

上と似ていますが。まあやりようはあるでしょう。

  • IaC

将来的には全般をコード管理としたい。Custom Runtime の部分も AWS SAM 等を使用して良い感じに管理したい。

少しずつ頑張っていきましょう。

CodeCommit CodeBuild CodePipeline Lambda CustomRuntime