serverless.ymlを一行一行、見ていくしかないんや


どうも、こんにちは。お元気ですか？

今年は夏が来るのが遅いな、と思っていたら、
すごく暑い夏をいくつか残していって
あっという間に過ぎていきそうですね。

[以前はServerless FrameworkやPythonについてなど
有識者の方々のを見たり、聞いたりして、教わりながら
色々と新しいことを体験しながら覚えていったので
それらの知見？ 感想？ をここに残しておきたいと思います。](https://qiita.com/han-aru/items/0f87c630147ecda5f629)

# 復習を兼ねて、serverless.ymlを一行一行、見ていこうと思います

まずはこんな記述がありました、と。

```yml
service: xxxxx-xxxxx
frameworkVersion: ">=1.53.0 <2.0.0"

plugins:
  - serverless-pseudo-parameters

custom: ${file(../../serverless.common.yml):custom}

provider:
  name: aws
  runtime: python3.8
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  profile: ${opt:profile, ''}
  logRetentionInDays: 30
  environment:
    TZ: Asia/Tokyo
    STAGE: ${self:provider.stage}
    API_ENDPOINT: ${self:custom.api_endpoint}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ssm:GetParameter
        - ssm:PutParameter
      Resource:
        - arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/*
    
functions:
  Refresh:
    handler: handler.xxx_handler
    layers:
      - ${self:custom.requirements_layer}
      - ${self:custom.shared_layers}
    events:
     - schedule: cron(0 16 ? * WED *) # １週間おき（UTC -> JST毎週木曜の1:00AM）に実行
```

# 見ていくぞ！ `英語ばっかり！`

### service: xxxxx-xxxxx
- ここでつけた名前がLamda関数やCloudFormationの一覧に登場します
- xxxxx-xxxxx-＜ステージ名＞-＜Lambda画面ではここにfunctionsの名前が追記されます＞

### frameworkVersion: ">=1.53.0 <2.0.0"
- バージョン範囲です、1.53.0以上、2.0.0未満、数値の左にある `frameworkVersion` に対して不等号が向けられてるんですね
- 公式ドキュメントによると `チームの全員がまったく同じ設定を使用し、予期しない問題が発生しないように、正確なバージョンに固定することをお勧めします` だそうです
- 正確なバージョンとは `frameworkVersion: "=1.0.3"` こう書くようです

### plugins:
- 色々とプラグインが出回っているようです（出回っているというと、海賊版みたいな雰囲気ありますね）
- Serverless Frameworkのメリットはこのプラグインが使えることにあるようです

### - serverless-pseudo-parameters
- [「serverless.ymlに文字列をいれておくと、デプロイ時にデプロイ先のAWSリージョンやAWSアカウント名に置換してくれます。 開発環境と本番環境で作成するS3バケット名などを変更したい時に使います」](https://dev.classmethod.jp/articles/serverless-framework-tips/)だそうです
- `BucketName: mybucket-#{AWS::Region}-#{AWS::AccountId}` こんな感じだそうです
- ちなみに `pseudo` とは `疑似` という意味でした（最初のpは発音しないらしいです）
- ハイフンが先頭についてる記述がいくつかあるのは、[各内容に応じてデータソースが異なるときは、記述の先頭にハイフンをつけて[- xxx]とすることで複数記述することが可能です](https://www.queue-inc.tokyo/n/na08a319b5194)とのことです、ありがとうございます
- この先頭にハイフンを書くのはYAML形式の特徴なんですね

### custom: ${file(../../serverless.common.yml):custom}
- ここから `../../serverless.common.yml` の位置にある、 `serverless.common.yml` の中身の `custom` に書いてあることを、ここでも `custom` という変数名で使うよ、と

### provider:
- プロバイダー（回線をインターネットと繋げる役割を担う接続事業者）ですね
- ここではクラウドサービスとかコンテナサービス（`azure` とか `Google Cloud Platform` とか）のようです（ご指摘いただきました、ありがとうございます）

### name: aws
- プロバイダーはAWSです

### runtime: python3.8
- ここで使用するpython言語のバージョンは3.8にします、と

### stage: ${opt:stage, self:custom.defaultStage}
- ステージ変数というらしいです
- 関数のステージはデフォルトで「dev」に設定されています
- 開発ステージ名はすべて `dev` と設定しておいて
- 本番では`prd`としておくと見分けがつく
- デプロイしたあとの設定名に必ずついてくる名前です
- opt:stageでステージ名は改めて `self:custom.defaultStage` にしますと言っています
- self（自分の）custom `../../serverless.common.yml` に書いてある `defaultStage` という欄を参照してください（ここではdevです）

### region: ap-northeast-1
- リージョン（地域）です
- ap-northeast-1はAWSの指定で `アジアパシフィック (東京)` になります

### profile: ${opt:profile, ''}
- プロファイル指定ですが、ここでは特使指定せず、デプロイ時に追記してください、もしくは、あとで指定があればここに、と
- プロファイルの詳しくは、先日私が書いた[「slsコマンドを使って、デプロイを行った」](https://qiita.com/han-aru/items/0f87c630147ecda5f629#%E5%88%9D%E4%BD%93%E9%A8%93%E3%81%97%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D%E3%81%AE%E8%A9%B3%E7%B4%B0)を参照

### logRetentionInDays: 30
- ログは30日間保存しててください、と設定

### environment:
- 直訳で環境ですが、ここでは環境変数となります（ご指摘いただきました、ありがとうございます）

### TZ: Asia/Tokyo
- タイムゾーン（地域ごとに標準時間は異なり、それらの標準時間帯）を`Asia/Tokyo`に

### STAGE: ${self:provider.stage}
- ここでも念のためstage `dev` を追記

### API_ENDPOINT: ${self:custom.api_endpoint}
- API（にアクセスするためのURL）は `cunstom` の `api_endpoint` の項目に書いてあります

### iamRoleStatements:
- IAMロールの設定ですね
- ステートメントは申告とかの意味もありますから、許可申請とかですね

### - Effect: Allow
- この下に書かれる項目の効果を許可（Allow）してます
- 不許可はDenyを使うと思いますが、不許可ならそもそも書かなくていいと思うので、基本Allowと思います

### Action:
- じゃあ何の動作を許可するの、と

### - ssm:GetParameter
- 今回はこれと

### - ssm:PutParameter
- この2つです
- ssmというのは、`AWS Systems Manager`のことで、その中にある、アプリケーション管理 ＞ パラメータストアの挙動の許可申告をしています
- パラメータストアというのはパスワードとかデータベースの文字列を暗号化などして、格納するのに使用します

### Resource:
- リソース：資源、仕様

### - arn:aws:ssm:${self:provider.region}:#{AWS::AccountId}:parameter/*
- 上の2つの挙動を誰に対して許可するの？ というのがここで指定されています
- arn（Amazon リソースネーム）:（の）aws（AWSリージョン）:（の）ssm:（は）${ここのプロバイダーのリージョンに準拠}:（し、）#{AWS::AccountId}
- [AWS::（擬似パラメータ）についてはここが詳しかったです。](https://oreout.hatenablog.com/entry/aws/cloudformation/4#:~:text=%E6%93%AC%E4%BC%BC%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AF%E3%80%81CloudFormation%E5%81%B4,%E3%81%9F%E3%82%81%E3%81%AB%E5%88%A9%E7%94%A8%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)ありがとうございます
- `擬似パラメータは、CloudFormation側で事前定義されているパラメータです` 擬似パラメータ -> `AWS::AccountId-AWSアカウントID`
- [`parameter/` はパラメータストア API アクションで、階層の 1 レベルのすべてのパラメータに対するアクセスを許可または制限するには、次のように階層名と AWS Identity and Access Management (IAM) ポリシーを使用する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/security_iam_service-with-iam.html)そうです
- 例えばこんな感じで `parameter/Dev/ERP/Oracle/*`
- 今回は `parameter/*` パラメータ階層すべてのアクセスを許可してくださいと申請しています
- つまり自分がssmのパラーメタストアを使うので許可してね、と
    
### functions:
- 働きかけとか関数の意味らしいです
- ここからLambda関数の記述が入ります

### Refresh:
- これは私が任意でつけた名前です
- ここにつけた名前はLambda関数一覧で、1行目の `service` で付けた名前のあとに追記されます

### handler: handler.xxx_handler
- 取り扱う関数はありますか？ -> `handler.` pyの中の `xxx_handler` の関数をここで使います
- ちなみに `xxx_handler` の関数には `app.` pyの中の `event` の関数を使うと書いてあります

#### layers:
- AWSをプロバイダーとして使用している場合（`provider: name: aws`）、サービス内のすべてのレイヤーはAWS Lambdaレイヤー
- Lambda Layersは、複数の関数で共有されるコードとデータを集中管理する方法
- 下記の2つをレイヤーに分けて使用しますよ、と

#### - ${self:custom.requirements_layer}
- `../../serverless.common.yml` に記述してある `requirements_layer` を参照します
- ここでは `該当するCloudFormationの出力に設定されているキー` までの記述が書かれていました

## 勘違いして覚えたこと（消してもいいですが残しておきます）

#### layers:
- ここでは `layers` というディレクトリの中に別のserverless.ymlファイルを作っており、その中で `serverless-python-requirements` というプラグインを呼び出しています
- AWS Lambdaに分割してファイルをアップロードすることで、わかりやすさとデプロイの時間短縮ができている、と

#### - ${self:custom.requirements_layer}
- layerディレクトリの中の `serverless-python-requirements` というプラグインでできる、 `custom` を使用しますよ、と
- 上記の `custom` の中には `layer: true` という記述があり、それは、[Lambdaレイヤーに、依存関係を処理する方法を、配置すること](https://github.com/UnitedIncome/serverless-python-requirements#lambda-layer)だそうです
- また[要件が圧縮され、レイヤーが自動的に作成されます](https://github.com/UnitedIncome/serverless-python-requirements#lambda-layer)とも書いてありました

## さ、気を取り直して続きです！

### - ${self:custom.shared_layers}
- `../../serverless.common.yml` に記述してある `shared_layers` を参照します
- ここでは `該当するCloudFormationの出力に設定されているキー` までの記述が書かれていました

### events:
- Lambdaをどうやって実行しますか？
- ちなみにここはActionではないのはなぜ？
- [アクションはあなたがすることです、あなたは何かをするつもりであることを意味する「アクションを起こす」ことができます。イベントはパーティーのような大きなもののようなものです。「メインイベント」でさえ、一連のものの中で最大のものです。](https://hinative.com/ja/questions/1042448)だそうです、ありがとうございます。
- つまり、自分でなにかするのではなく、Lambdaに包括的にしてもらうので、Eventと読んでいるのかも知れませんね

### - schedule: cron(0 16 ? * WED *) # １週間おき（UTC -> JST毎週木曜の1:00AM）に実行
- 今回は `cron式` を使って、毎週木曜深夜1時に実施する旨を書きました
- 当初JSTという日本時間で設定していました（ `TZ: Asia/Tokyo` だったためです）が、どうやら、 `ここの標準時間はCloudWatchに依存するようで、実施したい9時間前（UTFとかGMTとか海外の標準時間）を指定する` ようです
- cronの中に書かれている `?` は、日または週日（=曜日）用の値で、疑問符である必要があるそうです
- [日] フィールドに 7 と入力し、7 日が何曜日であってもかまわない場合、[曜日] フィールドに ? を入力できるそうです
- 今回は水曜日（日本時間で木曜日）は指定して、何日を指定しないので[日] フィールドに `?` と入力しました
- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)によると `, - * ? / L W` の中のどれでも良さそうですが、日と曜日だけを表しているものとして `?` もしくは `L` で良さそうです

## 終わりに `長かった！`
間違いがあればご指摘いただければと思います
次はもっと長くなるであろう、Python部分の記述を1行1行見ていこうと思います

```
ある晴れた日の午後、道を歩いていたら、
赤い洗面器を頭に乗せた男が歩いてきました。

洗面器の中にはたっぷりの水、
男はその水を一滴もこぼさないように、
ゆっくりゆっくり歩いてきます。

私は勇気をふるって
『ちょっとすいませんが、あなたどうして、
　そんな赤い洗面器なんか頭に乗せて歩いてるんですか？』
と聞いてみました。

すると男は答えました。

『それは…』
```

えー、お察ししますー
それではこの辺で失礼いたしますー