LoginSignup
4
0

More than 5 years have passed since last update.

CustomResourceを使って、BigQueryのDataSetをCloudFormationのStackとして扱う。

Last updated at Posted at 2018-12-06

背景

  • 弊社では、「Infrastructure as Code」を進めている。
  • AWSリソースは全てCloudFormationで構築・更新されている。
  • ログ解析のためにGCPのBigQueryを使っている。

課題

  • 環境構築時に、ログを流し込む先のDataSetを作る必要がある。
  • そのたびに、手動でDataSetを作るのは辛い。

解決策

  • CustomResourceを使う。

CustomResourceとは?

  • CloudFormation経由でLambdaを実行できる機能。
  • CloudFormationからの呼び出しに応じてDataSetを作ったり、削除したりするLambdaを書けば良い。
  • 呼び出し時のリクエスト/レスポンスの仕様はココ

どんないいことがあるの?

  • AWS以外のリソースもCloudFormationのStackとして管理できる。
    • IaCのインタフェースとしてCloudFormationだけ考えていれば良い。
    • ServiceCatalogから呼び出せる。

実践

今回はGoでLambdaを書きます。

まず、リクエスト/レスポンスをマーシャル/アンマーシャルするためのstructを書きます。

type Request struct {
    RequestType           string          `json:"RequestType"`
    ResponseURL           string          `json:"ResponseURL"`
    StackId               string          `json:"StackId"`
    RequestId             string          `json:"RequestId"`
    ResourceType          string          `json:"ResourceType"`
    LogicalResourceId     string          `json:"LogicalResourceId"`
    PhysicalResourceId    string          `json:"PhysicalResourceId"`
    ResourceProperties    json.RawMessage `json:"ResourceProperties"`
    OldResourceProperties json.RawMessage `json:"OldResourceProperties"`
    ServiceToken          string          `json:"ServiceToken"`
}
type Response struct {
    StackId            string `json:"StackId"`
    RequestId          string `json:"RequestId"`
    ResponseURL        string `json:"ResponseURL"`
    LogicalResourceId  string `json:"LogicalResourceId"`
    PhysicalResourceId string `json:"PhysicalResourceId"`
    Status             string `json:"Status"` // SUCCESS or FAILED
    Reason             string `json:"Reason"`
    Data               Data   `json:"Data"`
}

type Data struct {
    DataSetName string `json:"DataSetName"`
}

Dataは CloudFormationのテンプレート内で、!GetAttを使って取り出せます。

次に、実際にDataSetを作ったり削除したりするコードを書きます。
エラーハンドルやクレデンシャルの読み込みなどは省略します。

func handle(req Request) {
    res := Response{
        StackId:            req.StackId,
        RequestId:          req.RequestId,
        ResponseURL:        req.ResponseURL,
        LogicalResourceId:  req.LogicalResourceId,
        PhysicalResourceId: uuid.New().String(),
        Status:             "SUCCESS",
    }

    // リクエストのPropertiesを受け取る。
    var prop map[string]string
    json.Unmarshal(req.ResourceProperties, &prop)
    res.Data.DataSetName = prop["DataSetName"]

    // bigqueryクライアントの作成。実際にはcontextやtokenが必要。
    bq, err := bigquery.NewClient(...)

    // RequestTypeごとに処理を変える。
    if req.RequestType == "Create" {
        bq.Dataset(res.Data.DataSetName).Create(ctx, &bigquery.DatasetMetadata{})
    }

    if req.RequestType == "Delete" {
        bq.Dataset(res.Data.DataSetName).DeleteWithContents(ctx)
    }

    if req.RequestType == "Update" {
        // Updateには未対応。
    }

    // 処理結果をCloudFormationに返す。
    client := &http.Client{}
    bytea, _ := json.MarshalIndent(res, "", "    ")
    resreq, _ := http.NewRequest("PUT", res.ResponseURL, bytes.NewReader(bytea))

    resreq.ContentLength = int64(len(string(bytea)))
    client.Do(resreq)
}

レスポンスはLambdaの戻り値として返すのではなく、リクエストで指定されたURLにPUTする形でCloudFormationとコミュニケーションするようです。

最後にmain()でハンドラをセットします。AWS公式のGo対応最高。

package main

import "github.com/aws/aws-lambda-go/lambda"

func main() {
    lambda.Start(handle)
}

CustomResourceを生成するCloudFormationのStackを作ります。

AWSTemplateFormatVersion: "2010-09-09"

Outputs:
  BigQueryDataSetServiceTokenArn:
    Value: !GetAtt BigQueryDataSetFunction.Arn
    Export:
      Name: BigQueryDataSetServiceTokenArn

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      Path: /
      Policies:
        - PolicyName: "root"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource:
                  - arn:aws:logs:*:*:*
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents

  BigQueryDataSetFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: handle
      Runtime: go1.x
      Timeout: 30
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: "example.com"
        S3Key:    "lambda/custom-resource-bigquery.zip"

あとは、DataSetを作るタイミングでResourceを定義すれば良いです。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  DataSetName:
    Description: ""
    Type: String
    Default: hogehoge_dataset_01

Outputs:
  DataSetName:
    Value: !GetAtt BigQueryDataSet.DataSetName

Resources:
  BigQueryDataSet:
    Type: Custom::BigQueryDataSet
    Properties:
      ServiceToken: !ImportValue BigQueryDataSetServiceTokenArn
      Region: !Ref AWS::Region
      DataSetName: !Ref DataSetName

同様に、パスワード用のランダム文字列生成といった手作業でやっていたことや社内システムとの連携なども、CloudFormationで完結することができます。

Lambdaを開発・保守するコストはかかりますが、使い所を見極めればCustomResourceはIaCをさらに進めてくれる存在だと思います。

4
0
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
4
0