Day27 讓練習專案改由 S3+CloudFront 上線了,不過網址還是 CloudFront 給的 d111111abcdef8.cloudfront.net,又長又記不起來。今天要幫它接上自己的 Domain,把 HTTPS 憑證換成自己 Domain 的,最後再把昨天遇到的 403 換成我們自己的 404 頁面ㄅ!
Day20 已經把 example.com 指到 EC2 了,所以今天不動它,另外開一個 Subdomain static.example.com 給 CloudFront 用:
| 網址 | DNS Record | 走哪條路 |
|---|---|---|
example.com |
A Record,指到 EC2 的 Elastic IP(Day20) | EC2+Caddy+container |
static.example.com |
CNAME Record,指到 d111111abcdef8.cloudfront.net(今天) |
S3+CloudFront |
Subdomain 不用另外買。只要有 example.com,底下的 www、static 這些名字,在 DNS 加一筆 Record 就有了。像是 Day20 開的 www.example.com 就是一個 Subdomain,今天的 static.example.com 也是用一樣的方法開的。
同一個 Domain 底下的 Subdomain,可以各自指到不同的地方。DNS 只負責告訴瀏覽器「這個名字要去找誰」,後面是 EC2 還是 CloudFront,它不在乎。
Day22 的憑證是 Caddy 在 EC2 上自動跟 Let's Encrypt 申請的。CloudFront 沒有可以讓我們裝 Caddy 的 Server,所以憑證改用 AWS 自己的憑證服務 ACM(AWS Certificate Manager):
| EC2 這條路(Day22) | CloudFront 這條路(今天) | |
|---|---|---|
| 誰發的憑證 | Let's Encrypt | Amazon(ACM) |
| 怎麼證明 Domain 是你的 | Let's Encrypt 連到你的 Server 確認,所以 80 port 要開 | 在 DNS 加一筆 ACM 指定的 CNAME Record |
| 憑證放在哪裡 | EC2 上的 Caddy | CloudFront 的各個據點 |
| 誰負責續期 | Caddy | ACM,只要憑證有在用、驗證用的 CNAME Record 還在 |
| 費用 | 免費 | 免費 |
有一個規則一定要記住:給 CloudFront 用的 ACM 憑證,一定要在 us-east-1,也就是 US East (N. Virginia)(美國東部維吉尼亞北部)。CloudFront 是全球服務,只認這個 region 的憑證,就算 bucket 在台北也一樣。今天會用 CloudFront 的流程申請,而它會自動建在 us-east-1。如果你想先到 ACM 自己申請,記得先把右上角的 region 切過去,不然等一下在 CloudFront 會找不到這張憑證喔!
今天的流程是這樣:
static.example.com 指到 CloudFront如果一開始就把 static.example.com 指到 CloudFront,但 distribution 還不認得這個 Domain、手上也沒有它的憑證,瀏覽器連過去只會看到憑證錯誤。而等 CloudFront 都準備好了才改 DNS,網址一接上就是好的。
如果你的 Domain 是 .dev、.app 這類瀏覽器一律強制 HTTPS 的(Day20 提過),或是主 Domain 的 HSTS 設定包含了 Subdomain(includeSubDomains),瀏覽器遇到憑證錯誤時,連「繼續前往」的選項都不會給,所以這個順序就更重要了。
到 CloudFront 的 Console,點進 Day27 建立的 distribution:
static.example.com,按 Next。us-east-1 有沒有這個 Domain 的憑證。我們還沒有,所以按 Create certificate 建立一張新的。Create a wildcard certificate 不用勾,我們只需要 static.example.com 這一個名字。
| 欄位 | 範例 |
|---|---|
| Record name | _3639ac514e785e898d2646601fa951d5.static.example.com. |
| Record type | CNAME |
| Record value | _98d2646601fa951d53639ac514e785e8.xxxxxxxxxx.acm-validations.aws. |
這一頁先不要關,接下來要把它加到 Cloudflare。
ACM 不會連到你的 Server,而是請你在 DNS 加一筆它指定的 Record。你加得上去,就代表你管得了這個 Domain 的 DNS,也就證明 Domain 是你的。
進入 Cloudflare 中的 example.com,找到 DNS → 記錄(Records),按新增記錄(Add record),跟 Day20 一樣新增一筆 CNAME:
| 欄位 | 這次填什麼 |
|---|---|
| 類型(Type) | CNAME |
| 名稱(Name) | 貼上 Record name |
| 目標(Target) | 貼上 Record value |
| 代理狀態(Proxy status) | 僅 DNS(DNS only,灰雲) |
| 存活時間(TTL) | 自動(Auto) |
儲存之後,檢查名稱那一欄有沒有出現兩次 example.com。有些 DNS 服務會自動在名稱後面補上 Domain,如果貼的是完整名稱,就可能變成 ….static.example.com.example.com,這樣驗證會失敗。
想自己確認 Record 有沒有生效,可以在 Mac 的終端機查查看:
dig +short CNAME _3639ac514e785e898d2646601fa951d5.static.example.com
有回傳 ….acm-validations.aws. 就代表加好了。
回到 CloudFront 的頁面按 Validate certificate,通常幾分鐘內就會驗證成功。
這筆 CNAME Record 之後不要刪喔!ACM 會在憑證到期前自動續期,續期時也要靠它確認 Domain 還是你的。
憑證驗證成功後按 Next,確認一下內容,再按 Add domains。
distribution 的 Last modified 會先顯示 Deploying,等幾分鐘變回日期和時間,就部署完成了。
到這裡,distribution 已經認得 static.example.com,也有它的憑證了,只差 DNS 還沒有指過來。
static.example.com 指到 CloudFront回到 Cloudflare 的新增記錄(Add record),再加一筆 CNAME。作法跟 Day20 開 www 一模一樣,差別只在目標(Target):www 指到自己的網域,static 指到 CloudFront:
| 欄位 | Day20 的 www |
今天的 static |
|---|---|---|
| 類型(Type) | CNAME | CNAME |
| 名稱(Name) | www |
static |
| 目標(Target) | example.com |
distribution 的 Distribution domain name,例如 d111111abcdef8.cloudfront.net |
| 代理狀態(Proxy status) | 僅 DNS(DNS only,灰雲) | 僅 DNS(DNS only,灰雲) |
| 存活時間(TTL) | 自動(Auto) | 自動(Auto) |
CNAME 的意思也跟 Day20 一樣:static.example.com 是 d111111abcdef8.cloudfront.net 的別名,瀏覽器查 static.example.com 時,會沿著它找到 CloudFront 的 IP。
代理狀態也一樣要選灰雲。Day20 選灰雲,是為了讓瀏覽器直接連到 EC2;今天則是要讓瀏覽器直接連到 CloudFront,拿到剛剛 ACM 發的憑證。如果選橘雲,Cloudflare 會擋在前面,瀏覽器拿到的會是 Cloudflare 的憑證,等於在 CloudFront 前面又多了一層 CDN。
DOMAIN=static.example.com
dig +short "$DOMAIN"
curl -sI "https://$DOMAIN/" | grep -iE '^(HTTP|x-cache|content-type)'
curl -sv -o /dev/null "https://$DOMAIN/" 2>&1 | grep -iE 'subject:|issuer:|expire date:'
結果會出現類似:
d111111abcdef8.cloudfront.net.
192.0.2.10
192.0.2.11
HTTP/2 200
content-type: text/html
x-cache: Hit from cloudfront
* subject: CN=static.example.com
* expire date: Apr 27 23:59:59 2027 GMT
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M04
| 看哪裡 | 正常的話 |
|---|---|
dig +short |
第一行是 d111111abcdef8.cloudfront.net.,後面接著幾個 IP |
curl -sI |
HTTP/2 200 和 content-type: text/html |
subject: |
CN=static.example.com,代表這張憑證是發給我們的 Domain |
issuer: |
裡面有 O=Amazon,代表憑證是 ACM 發的 |
expire date: |
憑證到期日,到期前 ACM 會自動續期 |
用瀏覽器打開 https://static.example.com,點網址列左邊的圖示,也可以看到憑證是 Amazon 發的。原本的 https://d111111abcdef8.cloudfront.net 還是可以用,兩個網址連到的是同一個 distribution。
如果 dig 什麼都沒回傳,先等幾分鐘再查。DNS 會把「查不到」的結果記住一段時間,如果加 Record 之前就查過這個名字,可能要等這段時間過去才查得到。
Day27 試過打一個不存在的路徑會拿到 403:bucket policy 只給 CloudFront 讀檔案的權限,S3 沒辦法告訴它「這個檔案不存在」。專案匯出時已經有一個 404.html,我們讓 CloudFront 遇到 403 時改回傳這個頁面,狀態碼也改成 404:
403: Forbidden。10,意思是這個錯誤結果在據點上快取 10 秒。/404.html。404: Not Found。
再試一次不存在的路徑:
curl -sI "https://$DOMAIN/abc" | grep -iE '^(HTTP|content-type)'
這次會看到 HTTP/2 404 和 content-type: text/html,用瀏覽器打開也會看到練習專案的 404 頁面了!
PS. 這樣設定之後,所有的 403 都會變成 404 頁面。之後如果 OAC 或 bucket policy 設壞了,看到的也會是 404,除錯時要記得這件事。另一種做法是在 bucket policy 多給 CloudFront s3:ListBucket 權限,讓 S3 自己回 404;這篇先用比較簡單的做法。
今天練習專案的 S3 版有了自己的網址,HTTPS 也換成自己 Domain 的憑證,不存在的路徑也會看到 404 頁面了。不過每次改內容,還是要自己 build、aws s3 sync、再清 CloudFront 的快取,有點麻煩。下一篇來調整快取的設定,再把這些步驟交給 GitHub Actions,push 之後就自動部署ㄅ!