back checkのプレビュー環境

これは3/18に開催された、ROXX社内LT大会の資料です。

概要

今回は下記の内容を紹介します

  • back checkのリポジトリの状況
  • プレビュー環境に求められる要件
  • 参考にしたOSS
  • 実際の昨日のアウトプット
  • 具体的な実装

back checkのリポジトリの状況

backcheckには下記の4つのリポジトリが存在しています

  • LP
    • LPのフロントエンドが実装されている
    • Nuxt.js製
  • admin
    • 管理画面のフロントエンドが実装されている
    • Nuxt.js製
  • front
    • ユーザが使う画面が実装されている
    • Nuxt.js製
  • API
    • back checkが使うすべてのAPIが実装されてる
    • Laravel製

プレビュー環境に求められる要件

上記の様に、それぞれが別のリポジトリで実装されているため、下記のケースを想定したレビュー環境が必要となりました

  • LP, admin, front, APIそれぞれどれかだけの変更だけが反映されているもの
  • 複数のリポジトリにまたがった修正が反映されているもの

また、それ以外にプレビュー環境として、下記の要件を満たす必要がありました

  • プレビュー環境同士は完全に独立していること
    • 新規機能も多いためDBスキーマの変更にも耐えられるよう、DBも独立していること
  • githubのChatOpsでできること
  • Auth0と連携ができること
    • 環境作成時に、callbackURLなどが自動的に反映されること
    • https化されていること
  • PRがクローズされたら、自動で環境が削除されること
  • k8sとかの知識は不要で、AWS上で動くこと
  • リソースの上限とかはあんまり気にしたくない

参考にしたOSS

色々と探していたら、下記のOSS(というかプレゼン用に作ったと思われるなにか)が見つかった

https://github.com/clareliguori/clare-bot

clare-botというボットに対して、 preview this とメンションするだけで、プレビュー環境を作ってくれるというもの。使用している技術スタックも

Built with GitHub APIs, AWS Fargate, AWS CodeBuild, Amazon ECR, and AWS CloudFormation

とのことで、実装も簡単そうだったのでこれを使うことにした。

一方これだけでは最初の要件(マルチリポでよしなに使える)が適応できないため、ソースコードをいじる必要があった

実際の機能の使い方

  1. GitHub上でプルリクエストを作成します。
  2. プルリクエスト上で「@roxx-bot preview this」とコメントします。
  3. roxx-botさんがちょこちょことコメントを残していきます。(最長で30秒ほどかかります)
  4. しばらく経つとそのプルリクエスト(=base branch)のコードが反映された環境が立ち上がり、URLが表示されます。
  5. backcheckの環境は1リポジトリでは完結しないので、もう一方の環境を立ち上げます。

    つまり、apiのプルリクエストであればfrontを、frontのプルリクエストであればapiのプレビュー環境を立ち上げます。

    1. @roxx-bot preview front」または「@roxx-bot preview front」とコメントします。
  6. しばらく経つとそのプルリクエスト(=base branch)のコードが反映された環境が立ち上がり、URLが表示されます。

  7. URLのリンクを踏むとプレビュー環境を見ることができます。

preview XXX のあとに、githubのPRのURLを貼り付けることによって、そのPRで立ち上がっているURLを、環境変数に適応できるため、複数PRにまたがる施策でも問題なくアプリケーションが動くようになります。

具体的な実装

立ち上げ時

bot

  1. 10秒ごとに自分宛てのメンションが来ているかの監視
  2. メンションが来ていたら、コメントの中身を解析し、環境立ち上げの関数を実行
    1. preview xxx のxxxの部分から、どのリポジトリの環境を立ち上げるか判断
    2. xxx以降に指定されたPRのURLをよしなに解析し、そのリポジトリで必要となる環境変数を書き換え
  3. 環境用のユニークIDを発行(これが環境立ち上げ時のURLのサブドメインにもなる)
  4. Auth0にcallback URLなどを追加
  5. 対象リポジトリにあるbuildspec.ymlをもとにCodeBuildを開始
  6. CodeBuildからCloudFormation用のymlが出力されるので、それをもとに対象環境を立ち上げ
  7. 立ち上げ後、成功したらGitHubのコメントにURL付きで通知

リポジトリ

  1. bot側から渡ってくる各種環境変数を変換し、必要に応じてdocker buildするときに使う
  2. gitのコミットハッシュをdocker tagとしコミット→ECRにpush
  3. 生成したイメージのタグ等を用い、CDKを実行(以下CDKの実装内容)
  4. 環境変数の中で、ECSの実行時に使うものだけを抽出し、環境変数
  5. APIの場合はapimysqlを立ち上げ、フロント系の場合はnuxt generateしたものをnginxで静的ファイルとして参照する

cleanup時(bot側のみ)

  1. Githubのステータスがcloseになったことを検知
  2. Auth0のcallback URLなどを削除
  3. CloudFormationのスタックを削除
  4. 削除が完了し次第GitHubのコメントに通知

課題は解決したのか

課題 結果 コメント
他の環境に依存しない環境を立ち上げられたか AWSのフルマネージドな環境なので、無意識に無限に増やせる
Auth0との連携はできたか botをTypeScriptで書いてるので柔軟性が高くそれほど困らずに作れた
複数リポジトリのPRにまたがる環境を作れたか 一応作れたが、こういうのを作るためにモノレポ化はしたほうがいいなと改めて認識
デプロイの速度 docker cacheとか色々挟んだが、どうしても時間はかかってしまう。何故かECSのタスクをアップデートするときに、過去のタスクが削除されるまでの時間が長く、トータルの時間が長くなってしまっている
DBの独立性 backcheckはシーダーがいい感じに整っているので、特に問題なく環境構築はできた

今後の課題

  • ECSのタスクアップデート処理を見直して高速化
  • SSMを用いてAPIのコンテナ内に入れるようにする
    • プレビュー環境上でDBをいじりたかったりするので
  • 不要なコンテナは非稼働時には停止しておきたい
    • せっかくfargate使ってるのに、常に立ち上がっててコストがかかってる
  • botのコードのリファクタリング
    • 継ぎ足しになってるので不要なところとかは削除したい
    • 適度にモジュール化したい。現状は手続き型の典型的な処理の流れみたいになってる

PHPerKaigi 2020 に参加して、LTしてきました

こんにちは、今年の2月から、backcheck開発チームにジョインしました 秋葉です。
2020/02/09〜2020/02/11 に3日間に渡り開催された、PHPerkaigi 2020 に参加してきました。
今回自分は、スタッフとして色々やりつつ、ルーキーズLTの登壇者としてLTもしてきました。

phperkaigi.jp

これは参加者に全員配られたPHPerトレカ
f:id:akki_megane:20200215192447j:plain

ルーキーズLTしたよ

2/10 Day1 に
「明日からフロントもよろしく!」と言われたときに備える Atomic Design でのフロントエンド設計
というタイトルでルーキーズLTに登壇してきました
(プロポーザルを出すときに、Atom Design で出しているの前日に気づいてとても焦りました、Atomic Design が正しいです)
資料はこれ
speakerdeck.com

2019年に学んだことの一つとして、Atomic Design を使ったフロントエンドの設計があり、そのアウトプットという意味で登壇しました。 5分のLTということもあり、Atomic Design については触りの部分しか話せませんでしたが、興味を持ってくれる方がいればありがたいです。

反省

登壇時に緊張して、画面ばかりを見てしまいました。。。
理由としては登壇直前に、自己紹介のページを PHPerトレカに差し替え、
自己紹介を「ぼくの効果は 対戦相手のカードを1枚捨てることです! なのでみなさん手札を捨ててください」という自己紹介が ややうけ という結果に終わり(自分は大爆笑になると思っていました (泣))
登壇前は緊張していなかったのに、100人規模をややうけを見て緊張が一気に上がり、それ以降まともにお客さんの顔が見れませんでした。。。
終わった後に何人かに「いつもより緊張してたね」的なことを言われてしまい、若干凹みました。
緊張したとはいえ、カンファレンスでの登壇はいい経験になりました。
聞いてくださった皆様ありがとうございました。

アンカンファレンスでもLTしたよ

友人がトラックCで、ゲリラ的にLTしていたので自分も飛びこんで、LTを2回してきました。

  • 入門無限LT
  • AWS re:Invent 2019 俺的 注目技術

最初にやった、「入門無限LT」で、「会場にいるみんなにぜひこの場でLTやってほしいと」
けしかけたら、何人かの人が実際にLTに参加してくれてとても嬉しかです。

スタッフとして

スタッフのことについては、同僚の前田さんがいい感じにまとめてくれいますのでそちらもどうぞ。
https://techblog.roxx.co.jp/entry/2020/02/13/113611

事前準備

去年のPHPerkaigi2019年に引き続きスタッフとして参加しました!
今年はコアスタッフとして事前準備から参加していましたが、事前にやることや各種フローが整備されていて、
初めてのコアスタッフでしたがスムーズに割当られた仕事をすることができ、さすがカンファレンスジャンキーの集まりだと感動しました。

当日

自分は当日ルームBの担当をしながら、トークを聞いたり、参加者の方と交流したり、例年通りとても楽しく過ごせました。
大きな問題もなく無事に3日間やりきって、疲れましたがとても楽しかったです。
来年もスタッフやりたいなーと自然と思えました。

まとめ

カンファレンスで初登壇さらに初めてのコアスタッフで、資料作りや、事前準備等、個人的に慣れない作業で少し大変でしたが、
当日は最高に楽しく、登壇も楽しかったし、スタッフ業も楽しかったです。 
去年に引き続き、今年のPHPerkaigiは最高のカンファレンスでした!

ルーキーズLT × 1
アンカンファレンスLT × 2
懇親会LT × 1
計 4 回LTをしたので今回のPHPerkaigi一番LTをしたのは僕です!

GitHub CLIとhubを使い比べての感想

agent bank開発部の森です。 最近GitHubのPull Requestをローカルでcheckoutする案内に GitHub CLI Beta という項目が追加されましたね。

f:id:jiska_roxx:20200214124120p:plain
GitHub CLIの項目が追加されている

初めてみたときは hub がリネームでもしたのかな?と思っていたんですが実際はhubとは別のツールでした。 GitHub CLIとhubとを使い比べてみての感想をまとめます。

hubとの違い

GitHubCLIツールといえば hub が数年ほど前からあります。なぜhubがあるのになぜ別のツールを作ったのか? https://github.com/cli/cli#comparison-with-hub に違いが記載されているので引用します。

For many years, hub was the unofficial GitHub CLI tool. gh is a new project for us to explore what an official GitHub CLI tool can look like with a fundamentally different design. While both tools bring GitHub to the terminal, hub behaves as a proxy to git and gh is a standalone tool.

...そもそもhubがunofficialだったなんて知らなかった…公式プロジェクトとするべくhubとは異なる設計でつくられたそうです。とりあえず試しに使ってみましょう。

GitHub CLI

https://github.com/cli/cli#installation-and-upgrading に記載されている手順に沿ってインストールしてください。インストールすると gh コマンドが実行できるようになります。

❯ gh --help
Work seamlessly with GitHub from the command line.

GitHub CLI is in early stages of development, and we'd love to hear your
feedback at <https://forms.gle/umxd3h31c7aMQFKG7>

Usage:
  gh [command]

Available Commands:
  help        Help about any command
  issue       Create and view issues
  pr          Create, view, and checkout pull requests

Flags:
      --help              Show help for command
  -R, --repo OWNER/REPO   Select another repository using the OWNER/REPO format
      --version           Show gh version

Use "gh [command] --help" for more information about a command.

GitHub CLIでできること

今のところできることはissueとprの閲覧、作成です。 hubよりもできることはだいぶ少ないですが、issueやprをCUIで作る時に案内が丁寧だったりブラウザでプレビューできたりするのがhubよりもいいところです。 issue, prのリストはあまりhubと見た目変わらないですね。

f:id:jiska_roxx:20200214124531p:plain
gh issue list
f:id:jiska_roxx:20200214124525p:plain
gh pr list

気に入ったのはissueを作成する時にブラウザでプレビュー画面を表示してくれるところです。まちがって誤字入りissueやpr作ってしまうのが恥ずかしいので一度チェックできるのは嬉しい(チェックが漏れないとは言っていない)。

❯ gh issue create
Creating issue in jiska/example
? Title example
? Body <Received>
? What's next? Preview in browser
Opening github.com/jiska/example/issues/new/ in your browser.

公式ブログのアナウンス https://github.blog/2020-02-12-supercharge-your-command-line-experience-github-cli-is-now-in-beta/ に詳しい例が記載されていますのでそちらも参照ください。

今後期待したいこと

いまのところBetaだからなのか、僕がhubで重宝している機能、たとえば hub browse , hub ci-status , hub compare に相当する機能がGitHub CLIにはありません、今後の拡張を期待したいしcontributeがんばって追加してみようかな…

PHPerKaigi2020に参加して得た知見

こんにちは。bacckcheck事業部の前田です。
この記事は個人ブログの転載です。

phperkaigi.jp

PHPerKaigi2020に参加してきました。 一般参加ではなくスタッフとして参加したのでトークはきいてなく*1、スタッフとしての「PHPerKaigiの裏側!!」みたいなのも書かないので、あまりブログに書く内容がないのです・・・

そんな中で得た知見を少しでも紹介します。

朝のコンポタが最高

朝にモスバーガーで飲むコーンポタージュスープが最高です。 コンポタのためにスタッフ業をしているといっても過言ではないかもしれません。(過言です)
最高なので毎日行きました。

1日目

2日目

3日目

PHPerも踊れる

前夜祭にPHPrePartyというのがあり、なんとびっくり、DJイベントをやったのです。 普段のエンジニアの様子からはまったく想像つかないのですが、みんな踊れるんです・・・!!

ほしさきさんが動画をTwitterにあげていて、とても場の雰囲気が伝わってきます。

みんな場があればこんなにはしゃげるんだなーと再認識しました!

みんなコミュニケーションをとりたい

PHPerKaigiに参加して毎年思うことですが、みんなやっぱりコミュニケーションを取りたいんだな!!ということです。

当然に僕もコミュ障なわけですが、コミュニケーション下手ながらも、誰かと話したり、自分の知らない考え方をきいたりするのはとても楽しいのです。

そういうタイプは僕だけなのかな?と思ってたのですが、どうやらみんな同じ気持ちのようだとわかりました。

PHPerKaigiにはコミュニケーションスペースというものがあって、色々な人と話せる仕組みがあったりします。

f:id:chiroruxx:20200212213553j:plain

ここが本当にずっと賑やかで(トークやってるときもずっと!)、みんな楽しそうでした。

また、IRTもやっていて、ここもずっと満員でにぎわっていました。

f:id:chiroruxx:20200212213846j:plain

これは言ったら怒られるのかもしれないのですが、トークは後からでも情報を追えるんですよね。スピーカーのみなさんは優しいのでスライドをWebにあげてくれるし、最近のカンファレンスは後で動画を上げてくれるところも多いですよね。

でもこうやって誰かと話したり意見交換したりすることは、実はイベントに来ないとできないことなんです。

なのでみんな、色んな人といっぱい話すためにカンファレンスに行こう!PHPerKaigiに行けばいっぱい話せるよ!もちろんコミュ障でも!

と言っていきたいと思いました。

余談

今回はトレカが受付時に配られていて、PHPrePartyから本格的に交換していきました。
80人くらいの人と交換できて、わりと満足です。

ちなみに僕のカードはこれ。 f:id:chiroruxx:20200212215034j:plain

対戦相手はカードを1枚捨てる

うーん、自分の性格がでてるなぁ。

*1:私はリーダーだったのできかなかったのですが、他のスタッフは普通にトークをききにいけます

CDKとterraformの使い分け

こんにちは、 kotamat です。

少し前から、CDKを用いた環境整備を行いました。今までterraformで構築してきたので、それとの差分をメモ代わりに書こうと思います。

TL; DR

使い分けとしては下記のようになるかなと思っています

  • CDK
    • ベストプラクティスを簡単に構築したい場合。
    • ビルドプロセスなど、動的にインフラ構成を構築したい場合
  • terraform
    • サービスの基盤となるインフラを構築したい場合
    • 同等の環境を、異なるVPCなどで構築したい場合

CDK

とは

AWS クラウド開発キット (AWS CDK) は、使い慣れたプログラミング言語を使用してクラウドアプリケーションリソースをモデル化およびプロビジョニングするためのオープンソースのソフトウェア開発フレームワークです。

https://aws.amazon.com/jp/cdk/

簡単に言うと、TypeScript、PythonJava、および .NET でCluodFormation(AWS専用のIaCサービス)用のパターンファイルを生成してくれるものとなります。 いつも使い慣れた言語を使うことができるため、サーバーサイドのエンジニアが手軽にインフラの構築を実現できるものとなります。

使用感

今回はTypeScriptを用いたのですが、型推論がしっかり効いていたため、どのリソースにどのようなパラメータが設定可能なのかを、コードをみるだけである程度推測することが可能で、流れるようにコードを書くことができました。

const app = new cdk.App()
new ApiStack(app, 'Api', {
  env: {
    account: 'account',
    region: 'ap-northest-1'
  }
})
app.synth()

class ApiStack extends cdk.Stack {
  constructor(parent: cdk.App, name: string, props: cdk.StackProps) {
    super(parent, name, props)

    const vpc = Vpc.fromVpcAttributes(
      this,
      'Vpc',
      {
        vpcId: 'vpc-8932jkdw',
        availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'],
        publicSubnetIds: ['subnet-8302sba', 'subnet-32890231'],
        privateSubnetIds: ['subnet-9042389fdjosa', 'subnet-re032109jifds']
      })

    const cluster = Cluster.fromClusterAttributes(this, 'Cluster', {
      clusterName: 'api-cluster-name',
      vpc,
      securityGroups: []
    })
  }
}

このように、<リソース名>.from<設定するアトリビュート>()とすると、現在のリソースから情報を取得し、その取得した結果を別のリソース作成時に使い回すといったことができます。

実行時は下記のようにTypeScriptをJS化した上で通常のJavaScriptを実行するだけなので、環境変数の注入など、動的に指定することが可能です。

cdk synth --app 'node index.js' > ../template.yml

いいところ

上記で紹介したとおり、環境変数などを用いて動的にパラメータを注入できるわけですが、それが生きるポイントとしては、 何かをビルドしたときにインフラを用意する というユースケースが考えられます。

CDKを使っているということは必然的にAWSを用いているということになるわけですが、例えばあるリポジトリでCodeBuildで環境を整備しようとしたときにbuildspec.ymlを配置すると思うのですが、そちらに下記のように指定することによって、dockerイメージを作成したあとにそのイメージを使って環境を作るといったことが可能になります。

phases:
  install:
    commands:
      - npm install -g npm@6.12.0 aws-cdk@1.14.0

  build:
    commands:
    # ... docker build
      - npm ci
      - npm run build
      - cdk synth --app 'node index.js' > template.yml
artifacts:
  files:
    - template.yml

こちらのartifactsを使ってCloudFormationを立ち上げるといった具合です。 この際、例えばCodeBuildに対して特定の条件を分岐させるような環境変数を提供し、それをコード化することによって必要な環境をよしなに作ることができます。

const vpc = Vpc.fromVpcAttributes(
  this,
  'Vpc',
  {
    vpcId: 'vpc-8932jkdw',
    availabilityZones: ['ap-northeast-1a', 'ap-northeast-1c'],
    publicSubnetIds: ['subnet-8302sba', 'subnet-32890231'],
    privateSubnetIds: ['subnet-9042389fdjosa', 'subnet-re032109jifds']
  })
if(process.env.SOME_CONDITION){
  Cluster.fromClusterAttributes() //...ECSを立ち上げる
} else {
  Repository.fromRepositoryName() //... ECRからなにか取る
}

相性のいい環境

上記のようによしなに分岐ができるため、例えば特定の環境のみを再現したい場合の プレビュー環境 や、単純に日頃使い慣れている言語で、サクッと環境を作りたい サーバーサイドのエンジニア などがこの昨日を使うといいかもしれません。

相性が良くない環境

CDKは、 @aws-cdk/aws-ecs-patternsApplicationLoadBalancedFargateService など、便利な関数を呼び出す形で組み立てていきます。 型である程度中を推論できるものの、ハードコーディングされているオプションなどがあり、細かいチューニングには向かないかなと思いました。

Terraform

Use Infrastructure as Code to provision and manage any cloud, infrastructure, or service

https://www.terraform.io/

Terraformのサイトから引用したのですが、文字通り、AWS以外のクラウドインフラにおいて使えるIaCのツールとなります。

使用感

下記のように、独自のDSLを用いて記述していきます。 HashiCorpプラグインなどを使うことによって、設定できるパラメータが何があるかと言うのをある程度推論してくれます。

resource "aws_alb" "default" {
  name = local.alb_name

  security_groups = [
    aws_security_group.alb.id,
  ]

  subnets = data.aws_subnet_ids.main.ids
}

resource "aws_alb_listener" "http" {
  default_action {
    target_group_arn = aws_alb_target_group.default.arn
    type             = "forward"
  }

  protocol          = "HTTP"
  load_balancer_arn = aws_alb.default.arn
  port              = 80
}

実行は基本的にサイト上にあるバイナリファイルをダウンロードしてきて、それを実行していく形になります。

terraform plan # 仮実行
terraform apply # 本実行

実行後はstateというストレージに環境全体の情報が格納され、次回以降の実行時はこのstateと実際のインフラ環境、コードの状況を比較し、差分のみを実行するといったことを行ってくれます。

いいところ

一つ一つしっかり記述していくため、細かいチューニングができるのが特徴です。 また、バイナリの方でフォーマットのコマンドも用意されているため、基本だれが書いても同じようなコードになります。

また、importという機能があり、Webコンソールなどですでに構築されている環境の状態をstateに持ってくる機能があるため、試しにWebコンソールで環境を作ったあと、importでコード化するということができます。

相性のいい環境

基本的に運用し続ける環境で効果を発揮すると思われます。理由としては、

  • 差分適応による効率的な環境設定ができる点
  • import機能による、コード化の簡易性
  • インフラ構築に特化した、宣言型の実装

というところで、メンテナンスに適しているためです。 また、workspaceを用いることによって、本番環境と同等の環境を手軽に別の環境で作成することができるため、本番適応前の最終確認などが手軽に行えるといった点も評価できるポイントです

相性の悪い環境

すべてのパラメータを宣言的に書くという仕様上、あまり動的に変わるようなインフラ環境には向かないと思われます。 動的に変更したい場合はCDKを検討しましょう

まとめ

CDKとTerraformを比較してきました。それぞれ一長一短あると思いますが、よしなに使い分けていけるといいかなと思っております。

元記事

kotamat.com

lernaコマンドの標準出力が微妙な時はstreamオプションをつける

TL;DR タイトル

lernaはモノレポ管理下にあるpackage.jsonのコマンドを同時に実行することができる。

lerna run --scope s-* lint

とするとpackage.jsonのnameがs-で始まるすべてのワークスペースnpm run lintが実行される。(s-はオレオレパッケージのプレフィックスです)

f:id:apple19940820:20200120131838p:plain

モノレポではワークスペースでlintの設定を統一したり、ビルドのコマンドを統一したりするのでlernaのコマンド実行は便利。

しかし問題もなくはない。

標準出力がしょぼい

一部コマンドではルートのpackage.jsonにlernaコマンドを書いて実行すると、Warningになっていても標準出力に出ずsuccess!とだけ表示される。鬱なのでなんとかしたい。

lernaからの実行では一見パーフェクトに見えるが f:id:apple19940820:20200120125453p:plain

ワークスペースに潜ってyarn generateするとバンドルサイズ周りでwebpackが警告出していたという罠 f:id:apple19940820:20200120125604p:plain

--stream オプションをつけて子の標準出力をlernaに送る

runコマンドはオプションで--streamをつけると子プロセスの出力をストリーミングすることができる。各ワークスペースのコマンド実行ログがそのまま流れるため、lernaからの実行時でも重要な標準出力を見逃すことがない。

出力がごちゃ混ぜになってやばいみたいなこともない。

f:id:apple19940820:20200120130206p:plain

他にも並列実行のオプションや実行時のパフォーマンスを測定するオプションもある。需要に応じて使ってみると良いかもしれない。

元記事

ushirock.hateblo.jp

AWS Cloudfront をL7スイッチで使うならTerraform0.12 Dynamicが便利

こちらのブログは個人ブログと同じ内容です

kotamat.com

こんにちは、 kotamat です。

Terraform 0.12がでてしばらく立ちますが、先日構築したCloudfrontの環境において、Terraform0.12のdynamicを使わないと実現できない事案が発生したため、この際にと思い、一気に0.12にバージョンアップしました。

マイグレーション方法とかは、terraform側が提供しているものを使えば80%くらいはやってくれるので(一部手動で修正が必要ですが、WARNING出してくれるのでポチポチやっていくだけです。)今回は主題のdynamicについて紹介します。

Dynamicとは

terraform 0.12で追加されたシンタックスのうちの一つです。 通常、トップレベルのリソースに関しては、一つ一つのリソースに名前をつけて記述したり、 count とかを使って繰り返しを記述していきますが、security groupcloudfront のようなネストされたリソースを記述する際には一つ一つ重複して書く必要がありました。

特にcloudfrontは機能が多く、befaviorに各パスごとのoriginを書くことによってL7スイッチになることができるため、設定が繰り返し記述になりがちであり、かつネストが多い非常に複雑な構造になっているため、dynamicの恩恵を受けることができます。

記述方法

今回は

slides.com

で紹介した形を例に考えてみます。

各種スイッチの向き先の対照表は下記になっています。

ディレクト オリジンの向き先
/api/* API
/nova/* nova
/nova-assets/* nova
/nova-api/* nova
/vendor/nova/* nova
default S3(Nuxt.js)

今までであれば、/apiのブロックを一つ、novaのブロックを4つ、deafultのブロックを一つ書く必要がありましたが、dynamicを用いることによって、defaultとordered一つのブロックですべての表現をすることができます。

resource "aws_cloudfront_distribution" "main" {
// api
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
      "OPTIONS",
      "POST",
      "PUT",
      "PATCH",
      "DELETE",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.api_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/api/*"
  }
// nova
  ordered_cache_behavior {
    allowed_methods = [
      "GET",
      "HEAD",
    ]

    cached_methods = [
      "GET",
      "HEAD",
    ]

    default_ttl = 0

    forwarded_values {
      cookies {
        forward = "all"
      }

      headers = ["*"]

      query_string = true
    }

    max_ttl                = 0
    min_ttl                = 0
    target_origin_id       = local.nova_origin_id
    viewer_protocol_policy = "redirect-to-https"
    path_pattern           = "/nova/*"
  }
// 以下 nova-assets/*とかも記述していく

locals {
  path_patterns = [
    "/api/*",
    "/nova/*",
    "/nova-assets/*",
  ]
}
resource "aws_cloudfront_distribution" "main" {
  dynamic "ordered_cache_behavior" {
    // 何を繰り返すのかをfor_eachで指定する
    for_each = locals.path_patterns
    content {
      allowed_methods = [
        "GET",
        "HEAD",
        "OPTIONS",
        "POST",
        "PUT",
        "PATCH",
        "DELETE",
      ]

      cached_methods = [
        "GET",
        "HEAD",
      ]

      default_ttl = 0

      forwarded_values {
        cookies {
          forward = "all"
        }

        headers = ["*"]

        query_string = true
      }

      max_ttl                = 0
      min_ttl                = 0
      target_origin_id       = local.api_origin_id
      viewer_protocol_policy = "redirect-to-https"
      path_pattern           = ordered_cache_behavior.value // dynamicで指定したリソース名 + .valueでpathの中身を取得
    }
  }
}

他の使いみち

上記はbehaviorの設定をまとめるために使ってみましたが、例えばステージング環境は特定のパスだけBasic認証いれたいという要件が来たときには、今までだとlambda_function_associationだけ違う別リソースを記述する必要がありましたが、mapと組み合わせる事によって、下記のように同一ソースで記述することができます。

locals {
  lambda_associations = {
    "stg": {
      // stg特有のもの(Basic認証とか)
      {
        event_type = "viewer-request"
        lambda_arn = module.viewer_request.qualified_arn
      },
      // prodと共通のもの
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      },
    },
    "prod": {
      {
        event_type = "origin-request"
        lambda_arn = module.origin_request.qualified_arn
      }
    }
  }
}

resource "aws_cloudfront_distribution" "main" {
  default_cache_behavior {
    dynamic "lambda_function_association" {
      for_each = local.lambda_associations[terraform.workspace] // workspaceの値を取得し、動的にassociationを切り替える
      content {
        event_type = lambda_function_association.value.event_type
        lambda_arn = lambda_function_association.value.lambda_arn
      }
    }
  }
}

まとめ

terraform 0.12 dyanmicの使い方を、Cluodfrontを用いて紹介させてもらいました。 特に後者の、lambda edgeとの組み合わせは非常に使い勝手がいいので、よかったら試してもらえればと思います。