Serverless(node.js)で独自ドメインSSLの適用方法


[前回書いた記事](https://qiita.com/koshilife/items/af4be48db5029e887b82)ではS3に配置した静的コンテンツをSSLの独自ドメインで公開する方法について備忘しましたが、

今回はServerless Frameworkを使って、
独自ドメインの適用の仕方について調べて試した内容について備忘します。
前回同様、 ドメイン取得後数年寝かし続けていたドメイン　`koshikawa.net`　で検証しました。

# ゴール

- serverless でデプロイするwebサイトに独自ドメインを適用する。
- デプロイ時に指定する環境毎にサブドメインを変える。
  - `sls deploy --stage prd` なら koshikawa.net に反映される
  - `sls deploy --stage dev` なら dev.koshikawa.net に反映される

## 結果はこんな感じ

|本番 (koshikawa.net) prdとコンテンツは同じ　|本番 (prd.koshikawa.net)|開発 (dev.koshikawa.net)|
|:--:|:--:|:--:|
|<img width="300" alt="koshikawa.net.png" src="https://qiita-image-store.s3.amazonaws.com/0/51734/58270e39-38a8-a5a8-d05a-5616da416f4f.png">|<img width="300" alt="prd.koshikawa.net.png" src="https://qiita-image-store.s3.amazonaws.com/0/51734/9da0b6c7-f9de-e037-8a70-05e009fbf4ec.png">|<img width="300" alt="dev.koshikawa.net.png" src="https://qiita-image-store.s3.amazonaws.com/0/51734/0de71398-32a9-7ec3-28b6-d6261d0cca9f.png">|

# やり方

- [serverless-domain-manager](https://github.com/amplify-education/serverless-domain-manager)　を使うだけ。

### 参考サイト

- [serverless.com - How to set up a custom domain name for Lambda & API Gateway with Serverless](https://serverless.com/blog/serverless-api-gateway-domain/)
  - Serverless本家の記事、ここをなぞったら基本的なことはできました。
- [CircleCI と serverless を使いブランチごとに Lambda/API Gateway 環境を構築する](https://qiita.com/ooharabucyou/items/039b57474820ee9d803e)
  - 環境毎にデプロイ先のドメインを変更する具体的な設定例とかとても参考になりました。
- [serverless.com - Deploy a REST API using Serverless, Express and Node.js](https://serverless.com/blog/serverless-express-rest-api/)
  - Serverless本家のaws-node.jsでのREST APIのチュートリアル、serverlessでWEBアプリ作り方を思い出すために。

## 事前にやっておくこと

- 個別ドメインをRoute53でゾーン管理しておく、ACMの証明書を発行しておく。
- S3に　deploymentBucket を作っておく。
- コマンド実行するユーザに必要な権限を付与しておく。(serverless-domain-manager の README.mdに記載あり)

### 書いたソース 

```index.js
'use strict';

const serverless = require('serverless-http');
const express = require('express');
const app = express();

app.get('/', function (req, res) {
    res.send('Hello, world! on Serverless Framework');
});

app.get('/goodbye', function (req, res) {
    res.send('Goodbye, world! on Serverless Framework');
});

module.exports.handler = serverless(app);
```

### serverless.yml

```serverless.yml
# serverless.yml

service: ${self:custom.name}

plugins:
  - serverless-offline
  - serverless-domain-manager

provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs8.10
  stage: ${opt:stage, 'dev'}
  deploymentBucket:
    name: ${self:custom.name}.${self:provider.region}.deploys

custom:
  name: koshikawanet
  customDomain:
    domainName: '${self:provider.stage}.koshikawa.net'
    certificateName: '*.koshikawa.net'
    basePath: ''
    stage: ${self:provider.stage}
    createRoute53Record: true

functions:
  hello:
    handler: index.handler
    events:
      - http: GET /
  goodbye:
    handler: index.handler
    events:
      - http: GET /goodbye
```

## deploy前にcreate_domain

sls offline start でローカルで動作確認できたら、
`sls create_domain`コマンドにて以下を実行します。
・API Gatewayにカスタムドメイン作成および
・Route53に対して生成されたCloudFrontのFQDNのAliasを貼る

### 開発 (--stage devは省略可)

```
$ sls create_domain --stage dev
...
Serverless: 'dev.koshikawa.net' was created/updated. New domains may take up to 40 minutes to be initialized.
```

### 本番

``` 
$ sls create_domain --stage prd
...
Serverless: 'prd.koshikawa.net' was created/updated. New domains may take up to 40 minutes to be initialized.
```

※ 手元の環境では反映時間に40分もかからず10分くらいで反映されました。
  serverless.comの記事にも10分〜20分くらいって書いてあります。

１分待たずdeployを行ったところ、カスタムドメインに対してBase Path Mappingsの設定がされず、
`{"message":"Forbidden"}`が返却される事象となり、作り直しを余儀なくされたので、
余裕をもって行いましょう。

## deploy

### 開発 (--stage devは省略可)

```
$ sls deploy --stage dev
...
Service Information
service: koshikawanet
stage: dev
region: ap-northeast-1
stack: koshikawanet-dev
api keys:
  None
endpoints:
  GET - https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev
  GET - https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/goodbye
functions:
  hello: koshikawanet-dev-hello
  goodbye: koshikawanet-dev-goodbye
Serverless Domain Manager Summary
Domain Name
  dev.koshikawa.net
Distribution Domain Name
  xxxxxxx.cloudfront.net
Serverless: Invoke aws:deploy:finalize
```

これで https://dev.koshikawa.net にデプロイできました。


### 本番

```
$ sls deploy --stage prd
...
Service Information
service: koshikawanet
stage: prd
region: ap-northeast-1
stack: koshikawanet-prd
api keys:
  None
endpoints:
  GET - https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prd
  GET - https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prd/goodbye
functions:
  hello: koshikawanet-prd-hello
  goodbye: koshikawanet-prd-goodbye
Serverless Domain Manager Summary
Domain Name
  prd.koshikawa.net
Distribution Domain Name
  xxxxxxx.cloudfront.net
```

これで https://prd.koshikawa.net にデプロイできました。

# 本番サイト koshikawa.net に prd 環境のAPIをマッピングする

`prd.koshikawa.net`は本番サイトとしてドメインに環境名が入っていてかっこ悪いので、
`koshikawa.net`にprdと同じ内容が反映されるように設定します。

- API Gatewayのコンソール画面で カスタムドメイン koshikawa.net を作ります。
  - Base Path Mappingsの設定でprd環境でデプロイしたAPIを指定します。  
イメージ:  
<img width="300" alt="koshikawa.net-apigateway1.png" src="https://qiita-image-store.s3.amazonaws.com/0/51734/695d600c-7c55-f56a-eb80-0de87282b371.png">
- カスタムドメイン作成後にCloudFrontのFQDNが決まるので、Route53のゾーン設定画面にて`koshikawa.net`のAレコードとしてCloudFrontのFQDNをAliasとして設定します。  
API Gatewayコンソール上でのCloudFrontのFQDNの表示例:  
<img width="300" alt="koshikawa.net-apigateway2.png" src="https://qiita-image-store.s3.amazonaws.com/0/51734/8edf99fc-afba-e926-577c-688b7977198e.png">

### 参考: もちろんコマンドでも設定可能

[API Gateway でリージョン別 API 用のカスタムドメイン名をセットアップする](
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-regional-api-custom-domain-create.html)
>base-path-mapping の指定例:
>
```
aws apigateway create-base-path-mapping \
    --domain-name 'koshikawa.net' \
    --base-path '' \
    --rest-api-id xxx \
    --stage 'prd'
```


現場からは以上です。
寝かしていたドメインの育て方については依然検討中です。