ひぃ(hixi)の技術雑記ブログ

事実や解決策というよりも自分が思ったことをつらつらと書いていく所存。文章構成とかそういうのあまり気にせずに書きます

(続) CDK - aws-cdk-identifier

前回のブログ記事 のスライド上で こういった util 作った方がいいよって書きましたが、それをライブラリ化してみた。

今やっているプロジェクトではもうスタックの作り直しが出来ないフェーズなので、そちらでは適用できないけども、 cdk synth で問題ないこと確認済み。

ライブラリでの変更点

  • Identifier.cdkId は良くなかったので、 Identifier.stackNameIdentifier.constructName に分割
  • エラー処理の追加
  • Variables.resolve() の追加

まだスタックの作り直せる人は前の util のコピペからこっちに変更したほうが、シンプルなるので嬉しいかなーっておもいます (Cloudformation の名前からリソース名をつけるような物が全体的にシンプルになります)

typescript なり npm なりは初心者なので、なにか間違いとかあったら PR くれると嬉しいです。

もっとこういうのほしいから作ってみたとか。

英語めちゃくちゃやんけとか。

AWS CDK 使ってます

三日坊主 Cloudformation の記事を書いていましたが、最近は CDK を使っています。

ちょうど社内勉強会で話す機会があったので、 こちらにスライドを紹介させていただきます。 speakerdeck.com

そういえば、本番環境用の wrapper スクリプトも必要だなーってことで、適当に書いてみたのでそちらも。

./mycdk diff
./mycdk deploy

のように使っています

#!/usr/bin/env node

const AWS_PRODUCTION_ACCOUNT_ID = '{YOUR_PRODUCTION_AWS_ACCOUNT_ID}'

function confirm() {
  const yesno = require('yesno')
  const yes = yesno({
    question: "It's production environment. Are you sure you want to continue? ([y]es/[n]o)",
    defaultValue: null,
  })
  return yes
}

async function main() {
  process.argv.splice(0, 2)
  const command = process.argv[0]
  if (command == 'deploy') {
    console.log("checking environment...")
    const aws = require('aws-sdk')
    const sts = new aws.STS();
    const data = await sts.getCallerIdentity({}).promise()
    const accountId = data.Account
    if (accountId === AWS_PRODUCTION_ACCOUNT_ID) {
      if (!await confirm()) {
        return
      }
    }
  }

  const argv = process.argv
  
  const spawn = require('child_process').spawn;
  const deploy = spawn('cdk', argv, { stdio: 'inherit' })
}

main()

ちなみに ---profile は使って(使えて)いません。 AWS SSO を使っている環境においての cdk --profile がまだ対応していないためです。

なので、それぞれの環境の Command line or programmatic access から、ACCESS_KEY / SECRET を取得し、ターミナル上に貼り付けて deploy しています。 f:id:hixi-hyi:20200313150735p:plain

※ nodejs 初心者です

Go + AWS Service のテスト

python 用の AWS Service がテストできる GitHub - spulec/moto: A library that allows you to easily mock out tests based on AWS infrastructure. はとてもいいライブラリですね。

aws-cloudformation-lambda-ssm-put-parameter/test_index.py at a4aed513238354acd52f8d45fd9ef544ec8f2e7a · hixi-hyi/aws-cloudformation-lambda-ssm-put-parameter · GitHub みたくテストに mock とかくだけで、AWS のサービスを直接叩くことなく、ローカルで起動している AWS Service を利用してくれる。 とてもシンプルに簡単にテストを書かせてくれる。

さて、一方で Golang で AWS Service のテストはどのように書けばいいだろうか。 と調べると GitHub - localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline! という素晴らしいソリューションが出てくる(内部で moto も使ってる)

が、この localstack は言語非依存で AWS Service をローカルで起動するっていうもので、どの様な言語でも使えるものの、Go に最適化されているわけではない。 そのため Golang で愚直に使おうと思うと、if 文の分岐がプロダクションコードに挿入されることになる。 Lambda で言えば SAM LOCAL を使おうと思ったらそれ用の設定の IF 文。go test で使おうと思ったらそれ用の設定の IF 文。

普通に考えたらそんなの書きたくないから、どのようにか抽象化するっていうアプローチを取るとおもう。

ってことで、それを気にしなくて済むように GitHub - hixi-hyi/aws-client-go を作ってみた。

Lambda SAM LOCAL は残念ながらプロダクションと同じコードを用いてローカルにて実行するため、プロダクションコードに数行埋め込む必要があるが、テストコード用であればプロダクションコードに影響を与えることはない。

SAM LOCAL の場合のサンプルは GitHub - hixi-hyi/aws-client-go こんな感じ。Go SAM LOCAL の場合は Docker 上で動くため --docker-network $(docker network ls --filter "Name=localstack_default" --no-trunc -q) みたく、localstack docker とネットワーク的に疎通できるようにしておいて必要がある。 localstack の docker-compose の例はこんな感じ。

version: "3.3"

services:
  localstack:
    container_name: localstack
    image: localstack/localstack
    ports:
      - "4567-4600:4567-4600"
      - "8080:8080"
    environment:
      - SERVICES=sns
      - DEFAULT_REGION=ap-northeast-1
      - DOCKER_HOST=unix:///var/run/docker.sock
      - HOSTNAME_EXTERNAL=localstack
    networks:
      - localstack_default
networks:
  localstack_default:
    driver: bridge
    ipam:
      driver: default
sam local invoke Test --event test.json --region ap-northeast-1 --docker-network $(docker network ls --filter "Name=localstack_default" --no-trunc -q);

をすれば実行されます。

そして、 go test で利用する場合は、テストコード内で以下を呼び出しておけばいい。

awsclient.UseLocalStack(localstack.NewLocalStack())

※ この場合は localhost に対してのアクセスになる。 go test は docker を使ってない前提で。もちろん go test を docker を用いて実行するのであれば、 SAM LOCAL と一緒な感じ localstack などにアクセスするようにしてください。

awsclient の準備ができたら

    sess := session.Must(session.NewSession())
    client := awsclient.APIGateway(sess)

というように、必要なクライアントを受け取ればいい。AWS Service を使うのと LocalStack AWS Service を使うのに実装差分をなくすことができます。

サポートしているものは localstack のものだけになるので、それ以外の API を使う場合は自力でやんなきゃいけないところが難点ではあります。 ただ、結構サポートされているのと、 SNS / SQS などサポートされているので、Lambda で単純なものを書く場合や標準的な Web アプリケーションにおけるモックには十分なんじゃないかなっと。

Lambda + Go + CloudWatch Log での Structured Logging

なんか最近自分が見ている界隈でログの話が盛り上がってる気がする (GAE SE 2nd Gen)

GKE や GAE については、すでに以下の様な記事が出ているのでそちらを見ていただけるのがいいと思いますし、 Structured Logging の利点についてもまとまっていると思います。

StackDriver Logging における、 Structured Logging はとてもいいもので、僕も GAE を初めて使ったときには感動しました。 一方で、 AWS を使い始めたときに困ったのもログでした。 見やすいログ、調査しやすいログってとても重要なのにもかかわらず、AWS での標準のログでは従来のシンプルなログの出し方になってて、 要するに Debug("aaa") とすると、一行の aaa ってログが吐き出されるだけになっています。

そこで CloudWatch Log において StackDriver Logging のように出力するにはどうすればいいだろう。ってことで作ったのが GitHub - hixi-hyi/logone-lambda-go になります。 今回は Lambda 特化にしています。(仕事においては GAE FE Ruby 用だったり Fargate Go 用の同等のライブラリを社内で作って使ったりしています)

CloudWatch Log においては、何かしらのアノテーションを元にログをまとめ上げるという、効率の良い Log の収集方法はありません。 そこで、本モジュールにおいては、 ログを一旦メモリにためて、Lambda の終了時に JSON を組み立てて出力することにしています。 README の Caution にも書いてありますが、大きなシステムで使う際には注意してください。

システムによっては少し気にするポイントがあることは欠点*1ですが、それを考えたとしても非常に使いやすいログになると思っています。

使い方については

logone-lambda-go/example の実装と logone-lambda-go#outputs のアウトプットイメージを見てもらうのが早いと思いますが、例えば以下のようなことが可能になります。

  • { $.runtime.elapsed >= 1000 } で 1sec 以上かかったのログを取り出す
  • { $.runtime.severity = "CRITICAL" } で CRITICAL のログが出力されているものを取り出す
  • { $.runtime.tags.aws-sdk-error >= 1 } で Tag: aws-sdk-error が発生したものを取り出す

CloudWatch Log よりも見やすくなった CloudWatch Log Insight でも同様のことができますし、Log Insight の集計関数を使うことによって該当時間内にどれだけ何が起こったということを調べることも楽になります。 例えば fields @message | filter runtime.severity = "CRITICAL" | stats count(*) とすれば CRITICAL ログが吐き出されたイベントの数がわかります。

ということで、是非使ってもらえればと。

*1:メモリ使用量、同時に標準出力ができるログサイズ、panic 時の処理など

CloudFormation / Custom Resource Lambda との戦い

CloudFormation の分け方/作り方 (05/09追記) - ひぃ(hixi)の技術雑記ブログ で書いたけども CloudFormation はとてもいい。 全部記述できるし、例えば development / testing / production といった環境を作るのも一度作ってしまえばすぐ作れるし、 変更がコード化される、

が、全部 CloudFormation でやろうと記述をしていくと課題にぶち当たる。確実にぶち当たる。

CloudFormation は実はかゆいところに手が届かない。 再利用するようなものを定数定義したいとかエンジニアでは結構すぐに起こる発想だし、文字列をちょっと変えたいとかもよくある発想。

そういうプログラマティックに書きたい事があるっていうのもあるし、そもそも提供されていないリソースもある。 aws cli にいるけども、CloudFormation にはいないとかもよくある話。

じゃあ、その使いづらいままの基本文法部分を使うのか。 じゃあ、そのリソースが公式サポートされるまで待つ、そこだけ手動でやるのか。 そんなことしてたらいつまで立っても使えない。

さて、基本文法部分とリソースのサポートを並列に書いたものの、これらには大きな違いがある。 基本文法部分は DRY にできないとかそういった綺麗さな部分なのでまぁ譲ることは可能*1。 一方でリソースのサポートはそれがない限り使いものにならないって意味で非常に重要となる。

リソースのサポートの文脈で言えば、2つの異なった種類のものがあることも重要。

1つ目は新機能に対するサポート。 aws cli (RESTful API) ではサポートされているものの、 CloudFormation にまだ作られていないもの

2つ目はいつまで立ってもサポートされない機能。 SES などがいい例。 SES はリリースされてから結構立つものの、未だにサポートがされていない。

このことからわかることは、全部CloudFormationでやるためには、それを満たすものを自分で作らなければいけないということである。

前回の記事でもちょろっと触れたけども、その課題の解決策として AWS Lambda-backed カスタムリソース - AWS CloudFormation がある。 っていうか、これを使わずにすべてを IaC をやるのは不可能ってレベル。

なので、 CloudFormation を用いて

  • 記述式のインフラを構築したい
  • 周りの人にしっかりわかるようなインフラを作りたい

といった IaC 的な効果を期待をするようであれば Custom Resource Lambda を作ることにネガティブになってはいけない。 壁にぶち当たったらサクッと作れるようにしなければいけない。

といいつつ、 Custom Resource Lambda のリファレンスは非常に少ない。 じゃあどこで学ぶのかというとすでに書かれたものをみつつ、自分なりの書き方を模索するのが一番。*2

binx.io · GitHub はとてもいいリポジトリ群です。僕もかなり参考にさせてもらいました。 僕も基本機能はここにあるけども、ちょっと違うやつとかも作ったりしました。

自分も GitHub - hixi-hyi/aws-cloudformation-lambda: cloudformation custom lambda っていうのを作り始めてて、自分が作ったものは公開していくつもり。 まだ少ないけど使えるものあったら使っていってもらえると嬉しいです。 とりあえず今月中には SES 系は commit したい。

そのほかにも公開している人はいるので、これらを見ながら自分なりにサクッとコードを書ける土台を作るとよいです。 今から CFn を始めようと思う人は CFn の学習工数はもちろんのこと CFn Lambda の学習工数をちゃんととった上で挑みましょう。 そうしないと中途半端な IaC になってしまいがっかりすると思います。

※ もし一般アップロードしていいなら GitHub - hixi-hyi/aws-cloudformation-lambda: cloudformation custom lambda を参考にしつつ同じ様なフォーマットで作ってもらって、そして Commit してくれれば嬉しいかも!

ちなみに、 GitHub - awslabs/aws-cdk: The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code を使えば、DRY あたりについては解決しそう。 一方で、リソースのサポートという面でいうと CloudFormation と一緒だと思われる。(例としてすでに aws-cdk-examples/typescript/custom-resource at master · aws-samples/aws-cdk-examples · GitHub があるし、 example の書き方だと create / update / delete の phase を書くことができないし) CDK は使ったときないので使ってみたいと思いつつ、正直 CloudFormation だけで手一杯だし、学ぶことが多くなると大変。

*1: 譲れないケースについては AWS::Include Transform - AWS CloudFormation の利用が必要

*2:CloudFormation Custom Resource Lambda の作り方の注意点についてはまた書けたら。

CloudFormation の分け方/作り方 (05/09追記)

今まで CloudFormation (CFn) を細分化して管理していた。 例えば標準的なWebアプリの場合はこんな感じ。

./
├── cloudfront.yaml
├── fargate_cluster.yaml
├── fargate_service.yaml
├── loadbalancer.yaml
├── rds.yaml
├── acm.yaml
├── vpc.yam
└── subnet.yaml

CloudFormation で管理されてるので、インフラ構成がわかりやすいし、分けてあることによって修正箇所などが明確でよかった。 けども、順序通り実行しないといけないなどというデメリットがあった。 例えば、まず vpc を作って、 subnet を作って、そして次に... みたいな感じ。

それを解決するソリューションとして AWS::CloudFormation::Stack っていうのがあって、これはとてもいい。

以下のような fargate.yaml があったときに See also

AWSTemplateFormatVersion: 2010-09-09
Resources:
  Bucket:
    Type: Custom::Value
    Properties:
      ServiceToken: !ImportValue cfn-lambda-value:LambdaArn
      Value:
        Fn::Join:
          - ""
          - - "https://"
            - !ImportValue cfn-template-s3:DomainName
  Vpc:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub '${Bucket}/component/vpc/vpc-2az.yaml'
      Parameters:
        VpcCidrBlock: 10.1.0.0/16
        VpcDomain: vpc.local
        AzOne: !Select [0, !GetAZs '']
        AzTwo: !Select [1, !GetAZs '']
        SubnetPublicOneCidr: 10.1.10.0/24
        SubnetPublicTwoCidr: 10.1.20.0/24
        SubnetPrivateOneCidr: 10.1.11.0/24
        SubnetPrivateTwoCidr: 10.1.21.0/24
      TimeoutInMinutes: 60
   CloudFront:
.....

fargate.yaml を deploy するだけで、 VPC を作ってー、CloudFront 作ってーみたいな感じで全部作ってくれる。 この様なユースケースyaml を作っておけば、fargate の環境を構築したいときに順序など気にせずに deploy 可能。 f:id:hixi-hyi:20190507175535p:plain

ってことで、とても良いソリューションではあるものの、 CloudFront (CF) + Certificate Manager (ACM) を使って HTTPS を構築しようとしたときにハマる時がある。 僕たち日本人は往々にして ap-northeast-1 リージョンを使うことが多いと思う。 ここからは複雑っていうか前提がいろいろめんどくさくなるが、

  • CF は us-east-1 に作られるが、他リージョンの CFn からも作成可能 (逆に言うと CF は他リージョンから作っても US リージョンになる)
  • ACM はリージョンに紐付いて作成される( ap-northeast-1 の CFn から us-east-1 などに作れない)
  • CF に設定できる SSL 証明書は US リージョンの ACM しか使えない

ってことで、 us-east-1 以外のリージョンの CFn では CF を SSL 対応するまでを自動化できないわけである。 ここだけ手動が必要になるってこと。

CFn を使いたい人にとってそんなことはしたくないはずなので、解決する必要がある。 ということで、以下のようなものを作ってみた。 GitHub - hixi-hyi/aws-cloudformation-lambda-cloudformation-stack

これを使えば別リージョンに CloudFormation::Stack ができる。(※ 「ネスト」といったタグは残念ながらつかない) 正直いいアプローチなのかはわからない。そして、プロダクション環境では試していない。(2019/03/07現在)

CloudFormation はとてもいいソリューションだし、プラットフォームが標準でサポートしてるっていうのはいいことではあるものの、自分の経験則的には「標準で提供されている機能には限界があるので Custom Lambda を作る勇気が必要」だと思われる。

CloudFormation::Stack については本番環境を構築するにあたって時間が足りないという自分の怠慢で実現できなかったが、やっておけばよかったなとおもった。それほどよいものだとおもう。

すごい細かいこと含めて書くと以下のメリットがある。

  • 順序を気にする必要がなく、手順が単純化される
  • どのスタックがどの Output を利用するのかが親スタックを見るだけで明確になる。
  • CREATE FAILED したときに一旦 delete-stack しないと同じスタックは作れない。そしてそれで無駄に時間取られることがある。けど Stack の場合は Stack の親が一回成功してればそんなことがない

FAQ

  1. 今まではどうしてたの? 今まではスタックが細分化されていたので、 acm.yaml だけを us-east-1 に deploy して、 AWS Systems Manager パラメータストア - AWS Systems Manager を使って連携させてた

  2. 他に方法はないの? たぶん次のブログネタだけども、 AWS Lambda-backed カスタムリソース - AWS CloudFormationACM の multi-region バージョンを作るとか。 GitHub - hixi-hyi/aws-cloudformation-lambda-ssm-put-parameter こんな雰囲気で。 ACM の Custom Lambda を作るのと CloudFormation::Stack の Custom Lambda を使うべきかはまだ答えは出ていない。


03/09 追記 以下のコメントをいただきました

"AWS::CloudFormation::Stack"を使ったいわゆるNested Stack、初回デプロイには便利ですがUpdate StackするときにChangeSetを作っても、Nested Stackの内の差分確認ができないところは注意です。 "AWS::CloudFormation::Stack"という単位のリソースになってしまうのでその中身で何が変更/再作成/削除になるのかまで表示してくれないのです。。やるとしたらCFnを初回デプロイ時のみに利用する場合か、ステートレスなリソースのみ含めるようにして、データを保持するようなDBやEC2インスタンスはNestしないで外に出すように個人的にはオススメしたりします。

自分そういえば ChangeSet をちゃんと使ってないなーっておもった。 基本的に aws cloudformation deploy コマンド使っちゃってる

だいたい3日坊主

前回の投稿群が4年前 前々回の投稿群が6年前 ってことで、だいたい続けれないブログ。 Twitter もそんな感じ。

さて、そんな中で最近は Slack の times チャンネルにいろいろ投下してるわけだけども、雑でもいいからちゃんと文章に残しておいたほうがいいなって思ったので、やるぞという気持ちの投下をしてみた。

基本は今やってることをダンプしていく形として、一応現在進行系じゃないものも思い出しつつかければいいかなって思ってはいる。 この4年でやってた内容は多分こんな感じ

Golang はそこまで深くなにかがあるってわけじゃないので、多分基本クラウド系の話になればいいかなっと。 現在進行系でいうと AWS 特に Cloudformation なので、そのあたりは書いていければいいなっておもった。

決意の表明

※ 最近だとどういうところに書くのがいいのかなとか思って Qiita かなっておもったけども、なんとなく Qiita は問題解決系だと思ってて、こういうのがいいですとかこういう風にすればいいかも的な100%じゃないものは投下すべきじゃないかなってことで、はてぶ