AWS SAMを使う前にCloudFormationテンプレートを書こう


# はじめに
AWSでサーバレスの開発をするには[SAM](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/serverless_app.html)を使うという記事や話を聞いたことがある人は多いと思います。じゃあ、自分もSAMでやってみようと思ってやってはみたものの、SAMのテンプレートが書けない！って挫折した人が少なからずいると思います。私もその一人でした:scream::scream::scream:。おそらく、そのような人は、AWS CloudFormation のテンプレートを書いたことがない人だと思います。
実は、SAMのテンプレートは、CloudFormationテンプレートの拡張です。[AWS SAM Template Basics](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html)に、このように書かれています。

>AWS SAM templates are an extension of AWS CloudFormation templates. That is, any resource that you can declare in an AWS CloudFormation template you can also declare in an AWS SAM template. In addition, you can use the additional resource types provided by AWS SAM—for instance, the resources described in Declaring Serverless Resources—as shortcuts for some components of your serverless application.

Google翻訳してもらうと、こうなります。

> AWS SAMテンプレートは、AWS CloudFormationテンプレートの拡張です。つまり、AWS CloudFormationテンプレートで宣言できるリソースであれば、AWS SAMテンプレートでも宣言できます。さらに、AWS SAMが提供する追加のリソースタイプ（サーバーレスリソースの宣言で説明されているリソースなど）を、サーバーレスアプリケーションの一部のコンポーネントのショートカットとして使用できます。

また、このすぐ下にもこのように記述があります。

> For an introduction to AWS CloudFormation templates, see Learn Template Basics.

`Learn Template Basics`のリンク先が、CloudFormationのページにリンクが貼ってあるのです。CloudFormationのテンプレートが書ければ、SAMのテンプレートもかけるでしょう！ということで、**AWS SAMを使う前にCloudFormationテンプレートを書こう**と題して、私が、CloudFormationのテンプレートを書き始めた時のことを纏めてみました。


# 事前にやっておくこと
## まずは以下のページを読んで雰囲気をつかみましょう
- [Back Belt](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-aws-cloudformation)
- [【CloudFormation入門1】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築](https://dev.classmethod.jp/cloud/aws/cloudformation-beginner01/)
- [(2017年12月時点) 私的 CloudFormation ベストプラクティス](https://qiita.com/yasuhiroki/items/8463eed1c78123313a6f)

## 権限があることを確認しよう
- AWSアカウントの管理をしてない人は管理者に確認しましょう。もし、権限がもらえない場合はここで終了です:no_good_tone2:
- 権限については、[BlackBelt](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-aws-cloudformation)の82〜84スライドを参照しましょう

## テンプレートを記述する環境を整えます
### インストールする必要があるもの
- AWS CLI
- VS Code
    - 別のエディタでもよいですが、みんなが使っていると思うので。。。
- VS Code 拡張機能：CloudFormation
    - 完全に対応しているわけではありませんが、ある程度のことはやってくれますので私は重宝してます。
 ![image.png](https://qiita-image-store.s3.amazonaws.com/0/77333/72220861-ee42-d234-ed0b-48915c1475bc.png)

## そのほかにやっておくこと

- CloudFormationで管理したいサービスをConsole(GUI)で弄っておきましょう。一度も触ったことがないサービスをCloudFormationのテンプレートを書くのは難しいと思います。
- CloudFormationのテンプレートは、YAMLもしくはJSONで記述できますが、YAMLのほうが書きやすいと思います。もし、YAMLの書式を知らない場合はぐぐっておきましょう。


# まず簡単に作れるサービスから書いてみる
- 準備ができたら、まずはテンプレートを書いてみましょう。Lambda FunctionをCloudFormationテンプレートで記述するとちょっと面倒です。なので、まずはS3のように簡単に作れるサービスから記述するとよいと思います。(どのサービスも最初に細かい設定をするとどれもめんどいです。そこを誤解しないでくださいね）

- VSCodeで新規ファイルを作成し、言語モードをYAMLにしておきます。もしくは、sample.ymlで保存しておきます。
- VSCodeに`start`と入力します。そうすると、以下のように機能拡張が書いてくれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/77333/d701bdb1-8f0c-f111-9403-f9511e5a0c07.png)
- `Resources`に下にカーソルを移動させ、インデントした後で、`s3`と入力します。
 ![image.png](https://qiita-image-store.s3.amazonaws.com/0/77333/b79bccbe-7711-826e-b9f7-0f24c152dbd3.png)

このように候補が出てくるので、`s3-bucket`を選択します。そうすると、以下のようになります。機能拡張さまさまです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/77333/550a5a00-8ace-5aa9-1ae0-6915ba3d4ba1.png)

- 初回なのでまずは必須のところだけにしてみます

``` template.yml
AWSTemplateFormatVersion: 2010-09-09
Description: >
  Sample template 
Resources: 
  s3Bucket:
    Type: AWS::S3::Bucket
    Properties: 
      AccessControl:  BucketOwnerFullControl
      BucketName: (ここは一意になる名前を入力)
```
機能拡張は、`Bucketname`で書き出されますが、`BucketName`が正解なのでご注意ください。

# デプロイしてみます
デプロイは簡単です。

```
aws cloudformation deploy --template-file template.yml --stack-name sample-s3-0121
```
テンプレートファイルとスタック名を指定するだけです。
これで問題がなければ、

``` shell
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - sample-s3-0121
```
と表示されます。ここで、バケットの一覧を確認してみると、作成できていることが確認できると思います。



# テンプレートを書くために最低限知っておくべきこと
## 戻り値
サービスによっては、他サービスのARNを指定するようなことがあります。そのような場合は**戻り値**を使います。例えば、Lambda FunctionをRoleを指定するようなときは、`Role: !GetAtt iamRole.Arn`のように指定します。「iamRole」部は、論理 IDで指定したIDになります。
戻り値で何が取得できるのかは、[AWS リソースプロパティタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)に記述があるので、そこを参照しましょう
## 組み込み関数
テンプレート内で関数を使うことができます。最低でも、`!Sub`と`!Ref`、`!GetAtt`は使うことになると思います。

```yaml
      # Lambda関数名が展開される
      LogGroupName: !Sub /aws/lambda/${lambdaFunction}

      # パラメータを取得する
      Bucket: !Ref LogBucket

      # 属性値を取得する
      DomainName: !GetAtt S3Bucket.DomainName
```
最初のうちはこの３つがわかっていればなんとかなると思います。

## パラメータ
固有の値はテンプレートに書くべきではありません。再利用する際に変更が必要だし、Githubにあげた時などに事故につながることが考えられます。そのような値にはパラメータを使いましょう。

```yaml
Parameters: 
  MyBucketName:
    Type: String 
  Environment:
    Type: String
    AllowedValues: 
      - dev
      - stg
      - prod
    Default: dev
```
このように定義を行い、

```
aws cloudformation deploy --parameter-overrides MyBucketName="hogehoge"
```
というようにパラメータを渡しましょう

# ハマるポイントと解決策
ここまでできれば、後は、テンプレートをいろいろ書いてみるだけです！:sunglasses:
ここからは、私が書いた時にハマったポイントです。
### 初回のdeployからエラーとなり、テンプレートを更新してからdeployしたけど、以下のエラーが表示される

``` shell
An error occurred (ValidationError) when calling the CreateChangeSet operation: Stack:arn:aws:cloudformation:ap-northeast-1:***:stack/sample-s3-0121/5ffdf040-1d69-11e9-9f4f-0ec110ab8a1e is in ROLLBACK_COMPLETE state and can not be updated.
```
こんな時は、stackを削除してからdeployしましょう。削除するには、`aws cloudformation delete-stack --stack-name hogehoge`で削除できます。

## UPDATE_FAILEDになるけど、テンプレートのどこが悪いのかわからない
CloudFormationのステータスをコンソールから確認している場合、FAILになった理由は表示されるけど、どこが悪いのかわからないことがあります。よくあったのが、関数を多用していて、関数が処理された後のテンプレートは見れないためどこが悪いんだよ〜という場合です。このような時には、コマンドを叩いて、関数が処理されたテンプレートを確認しましょう。

``` shell
aws cloudformation describe-stack-events --stack-name {stack名} | grep "UPDATE_FAILED"
```
grepをかけないと、ROLLBACKされた正しいテンプレートも表示されるので注意しましょう。

## 何を指定していいのかわからない

同じサービスで既に動かしているものがあれば、コマンドでconfigを表示してみるとわかると思います。例えば、ClooudFrontのテンプレートを書いていてわからない時には、`aws cloudfront get-distribution-config --id hogehoge`です。

それでもわからない場合は、以下は参照しましょう:upside_down:

- 各サービスのAPIリファレンス
    - ActionsとData Typesは参照する場合があります。
    - [ガイドとAPIリファレンス](https://docs.aws.amazon.com/index.html?nc2=h_ql_doc#lang/ja_jp#user_guides)
- グーグル先生で検索
    - 検索すればいろいろとでてきます。それらを参照しましょう。


## テンプレートのチェックがOKでも、FAILすることがある

テンプレートの文法がOKでも、deployした時にFAILすることがあります。そういうものです。

```shell
aws cloudformation validate-template --template-body file://template.yaml 
```

`validate-template`に過度な期待をしないようにしましょう。

# 参照するページ
私がよく見るページをまとめました
## リファレンス
- [テンプレートリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-reference.html)
    - 特に、[AWS リソースプロパティタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)
- [テンプレートの分析](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html)
- [CloudFormationの条件関数を利用する](https://dev.classmethod.jp/cloud/aws/cloudformation-conditions/)
- [AWS ドキュメント | AWS](https://docs.aws.amazon.com/index.html#lang/ja_jp)
    -  各サービスのAPIリファレンス
    
## サンプルやTips
- [テンプレートスニペット](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/CHAP_TemplateQuickRef.html)
- [AWS CloudFormation のベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html)

## 更新情報
- [Release History](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ReleaseHistory.html)
    - 日本語のページは更新が遅いようなので、最新情報は英語版のページで確認しましょう

# 最後に
CloudFormationテンプレートの書き始めるために必要な点だけまとめました。書き慣れてないうちは、試行錯誤が続くことがありますが、検索すればいろいろと出てきます。皆さんの情報を参考しつつ、根気よく書き続けましょう。

## AWS CDK
まだ使ったことがありませんが、[AWS CDK](https://aws.amazon.com/jp/blogs/news/aws-cdk-developer-preview/)というものがあります。これにより、テンプレートを書く必要がなくなるかもしれませんね。。。