この記事は Wanoグループアドベントカレンダー2018の 8日目の記事になります。
8日目ったら8日目です。
AWSインフラ自動構築の必要性
仕事やプライベートでAWSをそこそこ使ってきたんですが、CloudFormationにはほとんどノータッチできてしまいました。
aws-sam-cliやserverless frameworkで必要があればちょっと設定を足してきた、くらいの事はやっていたんですが、基本生CloudFormationを書いていくのは複雑そうだという印象があり、ちょっと足が遠のいていたわけです。
実際「基本アプリ一個立ててCRUD」くらいのうちはGUI側でポチポチ...でなんとかなってきたケースも多かったのですが、もう少し複雑になるとそうもいかなくなってきます。
- SQSの多段キュー構成
- 複数サービスをまたいだイベントベースのそこそこ複雑なピタゴラ
- 複数AWSアカウント/複数region。それぞれプロダクション環境/dev環境毎に同じ環境
これらをチェック項目作って毎回ポチポチ立ててくのは流石に治安が悪すぎ、チームメンバーにも何がどこに設定されてるのか伝わりづらい..ということで、そろそろ取り組んでみるか...という気になりました。
goformation
cloudformationはjson/yamlの形式で定義できます。
しかし、独自の組み込み関数が多く、yaml1枚でまるでプログラミング言語のような様相を見せています。
まあ基本的に各プロパティの説明をドキュメント見ながら地道に書いていくしかないわけですが、もうちょっとなんとかならないかな、、ということで何かしらの定義ツールを使うことにしました。
そこで、awslabsの goformation を使ってみました。
aws-cdk + TypeScriptで書くという選択肢も考えたのですが、
- aws-cdkはCloudFormationをラップ/再定義した独自のモデルになりすぎている
- 現在のプロジェクトではサーバーサイドの実装にGo言語を使っているのでリポジトリに含めやすい
ということで、今回は学習がてら、こちらを使用することにしました。
SQS FIFO キュー/デッドレターキューを作ってみる
まず、よく作るパターンである 「SQSキューを作る」「可視性タイムアウトの時間を超えたらデッドレターキューに入る」という構成を作るとこまでやってみました。
yaml生成コード
package main
import (
"flag"
"fmt"
cfn "github.com/awslabs/goformation/cloudformation"
"io/ioutil"
"os"
)
var ENV string
func main() {
flag.StringVar(&ENV, "env", "dev", "env指定")
flag.Parse()
template := cfn.NewTemplate()
template.Description = fmt.Sprintf(`example1アプリの %s 環境です` , ENV)
pwd , _ := os.Getwd()
resources := NewResourceNames(ENV)
const MESSEGE_RETENSION_PERIOD_14DAYS = 1209600
template.Resources[LOGICAL_ID_SQS_DEAD_LETTER_QUEUE.String()] = cfn.AWSSQSQueue{
FifoQueue: true,
QueueName: resources.DeadLetterQueueName,
MessageRetentionPeriod: MESSEGE_RETENSION_PERIOD_14DAYS,
}
const TIMEOUT_SEC_5 = 1 * 5
template.Resources[LOGICAL_ID_SQS_FIFO_QUEUE.String()] = cfn.AWSSQSQueue{
FifoQueue: true,
QueueName: resources.FifoQueueName,
DelaySeconds: 3,
RedrivePolicy: map[string]interface{}{
"deadLetterTargetArn": cfn.GetAtt(LOGICAL_ID_SQS_DEAD_LETTER_QUEUE.String(), "Arn"),
"maxReceiveCount": 1,
},
VisibilityTimeout: TIMEOUT_SEC_5,
MessageRetentionPeriod: MESSEGE_RETENSION_PERIOD_14DAYS,
}
y, err := template.YAML()
if err != nil {
fmt.Printf("Failed to generate YAML: %s\n", err)
} else {
//fmt.Printf("%s\n", string(y))
}
err = ioutil.WriteFile(pwd + `/app/example0/export/template-`+ENV+`.yml`, y, 0777)
if err != nil {
panic(err)
}
}
定数系
type LOGICAL_ID string
func (self LOGICAL_ID) String() string {
return (string)(self)
}
const (
LOGICAL_ID_SQS_DEAD_LETTER_QUEUE LOGICAL_ID = `SqsDeadLetterQueue`
LOGICAL_ID_SQS_FIFO_QUEUE LOGICAL_ID = `SqsFifoQueue`
)
type ResourceNames struct {
DeadLetterQueueName string
FifoQueueName string
DeadLetterDetected string
DeadLetterOldest10Days string
}
func NewResourceNames(env string) ResourceNames {
return ResourceNames{
DeadLetterQueueName: fmt.Sprintf(`%s-example0-dead-letter-queue.fifo` , env),
FifoQueueName: fmt.Sprintf(`%s-example0-fifo-queue.fifo` , env),
DeadLetterDetected: fmt.Sprintf(`%s-example0-deadletter-detected` , env),
DeadLetterOldest10Days: fmt.Sprintf(`%s-example0-deadletter-oldest-10days` , env),
}
}
Makefile
ENV = dev
init:
dep ensure vendor-only=true
npm install -g cfn-lint
build-example-0:
go run ./app/example0/cfn.go -env ${ENV}
aws cloudformation validate-template --template-body file://app/example0/export/template-${ENV}.yml
cfn-lint validate ./app/example0/export/template-${ENV}.yml
deploy-example-0-dev:
aws cloudformation deploy --template-file app/example0/export/template-dev.yml --stack-name dev-example-0 --s3-bucket cfn-example-xxx --s3-prefix dev-example-0
aws-cliのバリデーションだけだと若干心許なかったので、yaml生成時にcfn-lintを同時にかけるようにしてあります。
生成されたコード
ENV=dev make build-example-0
します。
AWSTemplateFormatVersion: "2010-09-09"
Description: example1アプリの dev 環境です
Resources:
SqsDeadLetterQueue:
Properties:
FifoQueue: true
MessageRetentionPeriod: 1209600
QueueName: dev-example1-dead-letter-queue.fifo
Type: AWS::SQS::Queue
SqsFifoQueue:
Properties:
DelaySeconds: 3
FifoQueue: true
MessageRetentionPeriod: 1209600
QueueName: dev-example1-fifo-queue.fifo
RedrivePolicy:
deadLetterTargetArn:
Fn::GetAtt:
- SqsDeadLetterQueue
- Arn
maxReceiveCount: 1
VisibilityTimeout: 5
Type: AWS::SQS::Queue
以上のものが生成されました。
デプロイ
make deploy-example-0-dev
して先ほど生成されたtemplateファイルをデプロイします。
GUI上で確認すると、FIFOキューとデッドレターキューがそれぞれできてます。
デッドレターキューに移った。。
感想
CloudFormationを立てる上で大事な論理IDなどを定数化できるのはなかなか良いところで、デプロイ環境の違いなども無理にテンプレートの組み込み関数を使わず、コードで定義できるのでそこはとても気に入りました。
ただ、goで書いたSQSの RedrivePolicy の箇所に見られるように、身も蓋もない string
やmap[string]interface{}
での定義などが細部で出てくるので、そこは辛いところではありました。
この辺は徐々にアップデートされるでしょうし、元のコードの定義にjumpすれば詳細な説明やドキュメントへのリンクが必ず記載してありますので、AWSのインフラ自体を学習してく上でも理解が深まりそうです。