Fusic Tech Blog

Fusion of Society, IT and Culture

GoでCloudFormationを操る
2018/10/26

GoでCloudFormationを操る

どうもこんにちは。小原です。案件でよくAWSを使うことがあるのですが、今回はCloudFormationを使うことになりました。ただ普通にCloudFormationを使ってもつまらないですし、CloudFormationで作ったリソースに対して色々と操作する必要があるのでCloudFormationをGoのSDKを利用してみました。

CloudFormationのSDKを使うにあたって

AWSのSDKを使うときによくS3やEC2とか、メジャーなサービスを使ったサンプルや記事なんかはあるんですが、CloudFormationに関して全然ありませんでした。 あとは用語とライブラリ内の構造体がたくさんありどれがどれだかよくわからないのと関数がたくさんあるので、正直どれを使えばいいのか手探りでした。。。ここは地味に辛かったです、、

ライブラリ

ここら辺が必要

github.com/aws/aws-sdk-go/aws
github.com/aws/aws-sdk-go/aws/session
github.com/aws/aws-sdk-go/aws/credentials
github.com/aws/aws-sdk-go/service/cloudformation

パッケージ管理は今回はめんどくさいそこまで大きなアプリではないっていうのと、とりあえず動かすのが目的なんでそのままgo getでインストール。さくさくっとやって行きましょう。

クライアントの準備

ではでは、さくさくっとクライアントを用意しましょう。 ここは他のAWSのSDKと同様、アクセスキーとシークレットキー、リージョンを指定します。

cli := cloudformation.New(session.New(), &aws.Config{
    Credentials: credentials.NewStaticCredentials(os.Getenv("ACCESS_KEY"), os.Getenv("SECRET_KEY"), ""),
    Region: aws.String(region),
})

テンプレート

次にCloudFormationで使うテンプレートを読み込みます。

data, err := ioutil.ReadFile(`cloudformation/tempalte.yml`)
if err != nil {
  return nil, err
}
body := string(data)

stack := &cloudformation.CreateStackInput{
  StackName: aws.String(c.StackName),
  TemplateBody: aws.String(body),
}

ファイルにテンプレートを記載しているのでファイルの中を読み込みます。これは通常のCloudFormationで使うテンプレートファイルと同様のものになります。 そしてファイル内をcloudformation.CreateStackInput構造体のTemplateBodyに入れます。 ここはTemplateURLTemplateBodyどちらかを指定する必要があり、TemplateURLの場合は S3のバケットのURLを指定します。 そして、StackNameを指定します。これが最小構成のCreateStackInputになりますかね。

パラメータを指定

次にパラメータを指定していきます。構成管理ツールでよくあるのが変数みたいにパラメータを持つことができます。CloudFormationでもできるのでパラメータを指定して行きます。

res := []*cloudformation.Parameter{}

for _, v := range params {
    param := &cloudformation.Parameter{}
    param = param.SetParameterKey(v.Key)
    param = param.SetParameterValue(v.Value)
    res = append(res, param)
}

stack.SetParameters(res)

stack.SetParameters が実際にパラメータをセットしてる関数になります。 stackは上記のCreateStackInput構造体になります。SetParameters関数でCreateStackInput構造体にパラメータを追加します。 ちなみにパラメータはcloudformation.Parameterの配列で構造は以下のようなものになります。

type Parameter struct {
    ParameterKey *string `type:"string"`
    ParameterValue *string `type:"string"`
    ResolvedValue *string `type:"string"`
    UsePreviousValue *bool `type:"boolean"`
}

基本的にParameterKeyParameterValueに値を入れて入れば問題ないです。 これでテンプレートのパラメータに値を指定することができました。

Stackを構築

では今まで作ったCreateStackInput構造体を基にStackを作って行きましょう。

output, err := cli.CreateStack(stack)

はい、出来上がり!(あっさり)これでStackが作られ始めます。CreateStackの返り値でoutputには今回作るStackのStackIdが入った構造体が返ってきます。

これで終わりではない、、

はい、これで完成、、、っというわけにはいかないですね。 先ほどのCreateStack関数は作成されたStackIdを返します。ただこれはStack内のリソースが全て正常に起動されたらレスポンスをするのではなくStackができた瞬間にレスポンスを返します。つまりStack内のリソースが正常に起動できてるかはこの段階では判断できません。

なので、定期的にStackのステータスがCREATE_COMPLETEになったかどうか確認する必要があります。 最後にここのチェック処理を実装していきます。

describe := &cloudformation.DescribeStacksInput{
  StackName: aws.String(StackName),
}
timer := 10
timeout := 600
resFlg := true
for resFlg {
  if timeout <= 0 {
    return false
  }

  out, err := cli.DescribeStacks(describe)
  if err != nil {
    return nil, err
  }
  if out == nil {
    resFlg = false
    break
  }
  for _, v := range out.Stacks {
    if "CREATE_COMPLETE" == *v.StackStatus {
      resFlg = false
    }
  }
  if resFlg == false {
    break
  }
  time.Sleep(time.Duration(timer) * time.Second)
  timeout = timeout - time
}

DescribeStacks関数でStackの詳細情報を取得します。 ループの中でDescribeStacks関数を実行しステータスがCREATE_COMPLETEじゃなかった場合は10秒停止し、再度実行します。600秒の間にStackで完成しなかった場合はチェックするのをやめています(とりあえず無限ループに入らないため)。 これでステースがCREATE_COMPLETEになるまで待機し、全ての作成が完了したらループを抜け、その次の処理を行ってきます。

ここの処理はCREATE_COMPLETEのみしかチェックしてないため他のステータスも考慮する必要があるが、、 とりあえずこれで動くことを確認!! 無事にCloudFormationをGoから扱い、Stackの構築までできました(めでたしめでたし)

まとめ

Terraformとか他の構成管理ツールも検討しましたがGolangから実行したいっていうのと、作成されたリソースに対して次の処理を挟み込みたいのでCloudFormationにしました。

他のAWSリソースのSDKと同じような使い方なのでそこまで詰まることはなく、テンプレートさえできてさえいればさくさくっとできてしまいます。

今後の課題として、Stackのステータスの監視の実装がまだ不十分なのと更新処理等がまだできてないのでそこらへんの実装を進めて行きます。

kobaru

kobaru

インフラ好きっ子