おはようございます、こんにちは、こんばんわ ユニファでインフラみてますすずきです。
緊急事態宣言も解除されましたがリモートワークで引きこもり中です。
そんな中、TerraformでGithubからパッケージをダウンロードしながらLambdaにアップロードできるのか試してみたのでそれを書いてみます。
今回ダウンロードしてみるものはDatadog Forwarderです。
プライベートな話ですが、DatadogでLambdaのEnhanced Metricsを取得する記事を書いていたりするのですが(リンクは載せない)、それに利用するのがこのDatadog Forwarderです。
Datadog ForwarderをTerraformで導入しようとすると、Terraform使ってるのにCloudFormationを呼び出してYAMLを食わせるパターンになってしまいます。それはやりたくないということでダウンロードしてみる方法を試してみました。
※ 今回の最終的なコードGist
Terraform のいろんなProvider
TerraformはProviderを利用することでいろいろな機能を使えます。
AWSのProviderを使えばAWSを、GCPのProviderを使えばGCPを操作、管理でき、 クラウドサービスだけではなく、PostgreSQLなどの操作なども行えます。 変わり種だとJiraやGsuiteなどのProviderが存在しています。
また表題とは異なり TerraformでLambdaをデプロイする際は、Terraformと一緒にコードも保存しておき、 Archive Providerを利用して、コードをアーカイブしてからLambda(AWS Provider)にアップロードします。
利用するProvider
表題のようにTerraformでダウンロードしてLambdaへアップロードするには複数のProviderを組み合わせる必要があります。
利用するプロバイダーは以下
GitHubからDownload URLを取得
今回の場合ではリリースされているパッケージをDownloadしてくる必要があるので、GitHub Providerのgithub_releaseを利用して情報を取得します。
provider github { anonymous = true # GitHubの認証しませんよみたいな individual = true # 組織に属さない個人ですよみたいな } # リポジトリの情報を記入 data github_release dd_forwarder { repository = "datadog-serverless-functions" owner = "DataDog" retrieve_by = "tag" release_tag = var.release_tag # いま時点の最新(aws-dd-forwarder-3.12.0)を指定 }
github_releaseで取得できる情報はいろいろあるのですが、必要なのは asserts_url
になります。zipball_url
などを利用するとすぐダウンロードできるURLとなっているのですが、 pip install
などされてないSource codeが帰ってくるので利用できません。
仮にそれをLambdaにアップロードしてもファイル構造が違うので動いたりしません。
asserts_url
の現在の中身 : https://api.github.com/repos/DataDog/datadog-serverless-functions/releases/27002556/assets
# Terraformを実行して確認したい場合これを追加 output datadog_forwarder_asserts_url { value = data.github_release.dd_forwarder.asserts_url }
これをブラウザで見てみると以下のJsonが帰ってきます。
[ { "url": "https://api.github.com/repos/DataDog/datadog-serverless-functions/releases/assets/21152267", "id": 21152267, "node_id": "MDEyOlJlbGVhc2VBc3NldDIxMTUyMjY3", "name": "aws-dd-forwarder-3.12.0.zip", "label": "", "uploader": { "login": "DarcyRaynerDD", "id": 50632605, "node_id": "MDQ6VXNlcjUwNjMyNjA1", "avatar_url": "https://avatars1.githubusercontent.com/u/50632605?v=4", "gravatar_id": "", "url": "https://api.github.com/users/DarcyRaynerDD", "html_url": "https://github.com/DarcyRaynerDD", "followers_url": "https://api.github.com/users/DarcyRaynerDD/followers", "following_url": "https://api.github.com/users/DarcyRaynerDD/following{/other_user}", "gists_url": "https://api.github.com/users/DarcyRaynerDD/gists{/gist_id}", "starred_url": "https://api.github.com/users/DarcyRaynerDD/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/DarcyRaynerDD/subscriptions", "organizations_url": "https://api.github.com/users/DarcyRaynerDD/orgs", "repos_url": "https://api.github.com/users/DarcyRaynerDD/repos", "events_url": "https://api.github.com/users/DarcyRaynerDD/events{/privacy}", "received_events_url": "https://api.github.com/users/DarcyRaynerDD/received_events", "type": "User", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 12235591, "download_count": 104, "created_at": "2020-05-28T18:01:46Z", "updated_at": "2020-05-28T18:01:47Z", "browser_download_url": "https://github.com/DataDog/datadog-serverless-functions/releases/download/aws-dd-forwarder-3.12.0/aws-dd-forwarder-3.12.0.zip" } ]
一番下に browser_download_url
という値が存在しているのでそれを利用したい、がブラウザで見た結果なのでこの情報をTerraformに取得させる必要があります。
HTTP ProviderでJson取得
JsonをGETしたいということで、HTTP Providerを使ったらいいじゃないか、とういことで以下のようにします。
data http dd_forwarder { url = data.github_release.dd_forwarder.asserts_url request_headers = { Accept = "application/json" } } # 結果が見たいので追加 output datadog_forwarder_donwload_url { value = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"] # json textをmapに変換後情報取得 }
実行して帰ってきた結果は https://github.com/DataDog/datadog-serverless-functions/releases/download/aws-dd-forwarder-3.12.0/aws-dd-forwarder-3.12.0.zip
となっているので先程のJsonと同じですね。
GitHub から Download する
URLも手に入ったからあとはファイルダウンロードの機能をつかえばいいよねと思うかもしれませんが、Terraformにそんなものはないのです。wget Providerとかwget,download functionなんてものはないのです。
そこで出てくるのがNull Provider、これを利用し空の枠を作ります。
※ Null以外でも動かせるかもしれないけど試してないのでNull、だが実際に利用するならNullを使うのが懸命かと
そこで、local-exec Provisionerを実行し、wgetを実行します。
※ TerraformのDocker Image(0.12.24しか試してませんが)はwgetは存在しているがcurlは存在しない
実際に書いてみたものはこちら
# ローカル変数設定してるだけ locals { ddf_dl_path = format("%s/%s.zip", path.cwd, var.release_tag) } resource null_resource dd_forwarder { provisioner local-exec { command = "wget $dl_url -O $dl_path" environment = { dl_url = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"] dl_path = local.ddf_dl_path } } }
これを実行すると、頑張ってダウンロードしている結果が見れます。
null_resource.dd_forwarder: Creating... null_resource.dd_forwarder: Provisioning with 'local-exec'... null_resource.dd_forwarder (local-exec): Executing: ["/bin/sh" "-c" "wget $dl_url -O $dl_path"] null_resource.dd_forwarder (local-exec): Connecting to github.com (52.192.72.89:443) null_resource.dd_forwarder (local-exec): Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (52.216.137.108:443) null_resource.dd_forwarder (local-exec): saving to '/workdir/aws-dd-forwarder-3.12.0.zip' null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 3% |* | 373k 0:00:30 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 8% |** | 1036k 0:00:21 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 10% |*** | 1223k 0:00:26 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 11% |*** | 1410k 0:00:29 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 14% |**** | 1682k 0:00:30 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 15% |***** | 1886k 0:00:32 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 17% |***** | 2141k 0:00:32 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 20% |****** | 2413k 0:00:31 ETA null_resource.dd_forwarder: Still creating... [10s elapsed] null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 22% |******* | 2702k 0:00:30 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 25% |******** | 3008k 0:00:29 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 28% |******** | 3348k 0:00:28 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 31% |********** | 3772k 0:00:26 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 36% |*********** | 4366k 0:00:22 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 43% |************* | 5167k 0:00:18 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 51% |**************** | 6136k 0:00:14 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 57% |****************** | 6850k 0:00:11 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 63% |******************** | 7598k 0:00:09 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 70% |********************** | 8380k 0:00:07 ETA null_resource.dd_forwarder: Still creating... [20s elapsed] null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 76% |************************ | 9196k 0:00:05 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 83% |************************** | 9961k 0:00:03 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 90% |**************************** | 10.5M 0:00:02 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 97% |******************************* | 11.3M 0:00:00 ETA null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 100% |********************************| 11.6M 0:00:00 ETA null_resource.dd_forwarder (local-exec): '/workdir/aws-dd-forwarder-3.12.0.zip' saved null_resource.dd_forwarder: Creation complete after 23s [id=5446667541515964428]
Lambda へアップロード
あとは、Lambdaへダウンロードしてきたファイルをアップロードしてやれば完了です。
provider aws { region = "ap-northeast-1" } resource aws_lambda_function dd_forwarder { filename = local.ddf_dl_path function_name = "lambda_function_name" handler = "lambda_function.lambda_handler" role = var.lambda_role runtime = "python3.7" depends_on = [ null_resource.dd_forwarder ] # ダウンロードが終わってから実行されるように順序調整 }
問題あり
前段ではTerrafomつかえば、ファイルダウンロードしてのアップロードもできるじゃんって思えそうですが課題はあります。
ローカルで手動実行しているぶんにはみえて来ないのですが、GitHub ActionやCircleCIなどでこのようなコードを実行した場合に2回目からは以下のようなエラーが出てしまいます。
aws_lambda_function.dd_forwarder: Creating... Error: Unable to load "/workdir/aws-dd-forwarder-3.12.0.zip": open /workdir/aws-dd-forwarder-3.12.0.zip: no such file or directory on main.tf line 48, in resource "aws_lambda_function" "dd_forwarder": 48: resource aws_lambda_function dd_forwarder {
これは、以下の要因で発生します。
- 1度目の実行時のNull リソースの管理情報が保存されてる
- terraform管理情報と差分がでないと処理が動かないのでwgetは動かない
- CI環境のストレージはリセットされている
- 1回目にダウンロードしたファイルは存在しない
- ダウンロードは終わったつもりでLambda(AWS Provider)がファイル操作しようとした
これを根本から解決するには、結局zipファイルをどこかに保存しておくかキャッシュするなどCI環境側での頑張りが必要だと思われます。
Terrarom側だけでなんとかするには null_resource
の処理を以下のように書き換えます。
resource null_resource dd_forwarder { triggers = { timestamp = timestamp() # 今の時間を返すfunction(randomとかでもよいかも) } provisioner local-exec { command = "wget $dl_url -O $dl_path" environment = { dl_url = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"] dl_path = local.ddf_dl_path } } }
triggersのmap情報が変更されるとnull_resource
は再度実行されるので、時間情報を渡すことで毎回実行するようしてファイルが存在し続けるようにします。
これはこれで、Terraformに差分がないのに毎回実行されて時間の無駄処理になってしまうので課題が残ります…。
課題は残りましたが以上で、TerraformでGitHubからダウンロードしてLambdaにアップロードする処理となります。
こうしたら解決したよとか、こうしたほうが良いとかご意見あればお声掛けいただけたらと思います。
すずき