LoginSignup
8
4

More than 3 years have passed since last update.

goformationで始めるCloudFormation (1) SQS/デッドレターキュー

Last updated at Posted at 2018-12-09

この記事は Wanoグループアドベントカレンダー2018の 8日目の記事になります。
8日目ったら8日目です。

AWSインフラ自動構築の必要性

仕事やプライベートでAWSをそこそこ使ってきたんですが、CloudFormationにはほとんどノータッチできてしまいました。
aws-sam-cliserverless 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キューとデッドレターキューがそれぞれできてます。
できてる.png

キューにメッセージを登録し
できてる2.png

観測します。
できてる3.png

デッドレターキューに移った。。

できてる5.png

感想

CloudFormationを立てる上で大事な論理IDなどを定数化できるのはなかなか良いところで、デプロイ環境の違いなども無理にテンプレートの組み込み関数を使わず、コードで定義できるのでそこはとても気に入りました。
ただ、goで書いたSQSの RedrivePolicy の箇所に見られるように、身も蓋もない stringmap[string]interface{} での定義などが細部で出てくるので、そこは辛いところではありました。
この辺は徐々にアップデートされるでしょうし、元のコードの定義にjumpすれば詳細な説明やドキュメントへのリンクが必ず記載してありますので、AWSのインフラ自体を学習してく上でも理解が深まりそうです。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4