Slide 1

Slide 1 text

大量リダイレクトも怖くない! CloudFront KeyValue Storeで サービスサイトリニューアルを楽々乗り越えた話 2024/7/5 DevelopersIO 2024 SAPPORO 生活協同組合コープさっぽろ 山﨑 奈緒美 木原 卓也

Slide 2

Slide 2 text

AWS SAMURAI 2015 JAWS-UGアーキテクチャ専門支部 JAWS-UG情シス支部 生活協同組合コープさっぽろ デジタル推進本部 システム企画部 インフラチーム 山﨑 奈緒美 ご挨拶と自己紹介 大阪出身。 就職で上京し、ソフトハウスでインフラエンジニア 地図情報システム開発会社でひとり情シス 旅行会社の情シス部門でクラウド担当 2020年9月に東京から札幌へ移住し10月よりコープさっぽろへJOIN。 AWSのことならなんでも担当。 @nao_spon I ♡ Route53 IAM Organizations 夏はロードバイク、冬はスノボしてます。仲間募集中!

Slide 3

Slide 3 text

生活協同組合コープさっぽろ デジタル推進本部 システム企画部 アプリ・サイトチーム 木原 卓也 ご挨拶と自己紹介 AWS Community Builder (Frontend Web and Mobile, since 2021) Amplify Japan User Group 運営メンバー 熊本出身の普通のソフトウェアエンジニア。 愛知県の大学進学後、なぜか北海道で就職。 システム開発の会社で、業務系からゲーム系まで各種開発業務に従事。 2021年4月からコープさっぽろへJOIN。 店舗系のシステム導入やAWS移行を担当し、 2024年3月よりアプリ・サイトチームへ参画。 Flutter 1年生。 I ♡ AWS Amplify 好きなフィギュアスケートの技 スプレッド・イーグル

Slide 4

Slide 4 text

生活協同組合コープさっぽろについて(※2024年3月現在) 設立年月日 1965年7月18日 組合員数 201万人(北海道総人口523万人の約38%) 出資金額 897億円 総事業高 3,186億円 職員数 14,743名(契約職員・パートアルバイト含む) 店舗数 109店舗 移動販売車 94台(134市町村) 宅配物流センター 41センター 10デポ 車両1,300台 配食工場 6工場(札幌、函館、苫小牧、旭川、釧路、帯広) 生産工場 石狩食品工場、江別食品工場、はまなす食品、江別物流センター 生鮮センター(PC)、ドリームファクトリー(函館)

Slide 5

Slide 5 text

コープさっぽろ 組合員200万人達成! 5 1965年の事業開始から57年で到達 道内全247万世帯のうち80%以上の加入率 2023年10月28日200万人達成セレモニー

Slide 6

Slide 6 text

生活協同組合コープさっぽろについて 6

Slide 7

Slide 7 text

生活協同組合コープさっぽろについて 7 コープさっぽろの仕事は生活の課題を聞き解決すること ITの力を使い課題解決の一助となるのが コープさっぽろのシステム部の仕事

Slide 8

Slide 8 text

コープさっぽろ インフォメーションサイト リニューアル 8

Slide 9

Slide 9 text

コープさっぽろのインフォメーションサイト 9 ※リニューアル後の画面です

Slide 10

Slide 10 text

コープさっぽろのインフォメーションサイト 10 ● 店舗情報の掲載 ● 店舗、宅配、生活関連、子育て支援等各サービスの入り口 ● WEBでの組合員加入の入り口 ● イベント、キャンペーン情報の掲載 ● コープさっぽろのプライベートブランド商品紹介 ● 組織情報の掲載(いわゆるコーポレートサイトの内容)

Slide 11

Slide 11 text

サイトリニューアルの経緯 11 ● およそ5年おきにサイトリニューアルを計画している ● サイト運営の課題点を解決するため

Slide 12

Slide 12 text

旧サイトでの課題点 12 ● セキュリティ面 ○ 全体でユーザーを1つしか発行できない ○ 担当者全員で1つのユーザーを共用している ○ ユーザー単位での権限分離ができない ○ 脆弱性診断での指摘事項あり

Slide 13

Slide 13 text

旧サイトでの課題点 13 ● 運用面 ○ ページの作成や更新は各部門で実施するため、共有アカウント運用は厳しい ○ HTMLの知識がないと設定できない項目があり作成・更新作業が属人化 ○ 権限分離ができていないため、誤って他部署のページを... ○ WEB制作会社が契約しているレンタルサーバー上で稼働しており、 バックアップや冗長化、パッチ適用等のコントロールができていない

Slide 14

Slide 14 text

旧サイトでの課題点 14 ● CMS機能面 ○ 公開中ページを編集・更新する際に予約投稿ができない ○ HTMLを記述しないと、テキスト装飾やタグの_blank設定ができない ○ テーブル機能が機能しない ○ コーポレートサイト側に、ページ複製による新規ページ作成機能がない

Slide 15

Slide 15 text

旧サイトでの課題点 15 ● デザイン面 ○ 当時の流行やコープさっぽろの風潮などから構成され、古くなっていた ○ レスポンシブデザインとは言い難く、スマホブラウザで見にくかった

Slide 16

Slide 16 text

リニューアル後の構成 16

Slide 17

Slide 17 text

リニューアル時における変更点 17 ● セキュリティ面 ○ CMSサーバーで生成したWEBコンテンツをS3に配置する ○ CMSとオリジンを分離することでCMSへの攻撃リスクを低減

Slide 18

Slide 18 text

リニューアル時における変更点 18 ● 運用面 ○ スケーラビリティを確保 ○ CMSのバックアップはスナップショットをとることで担保 ○ パッチ適用等のコントロールも可能

Slide 19

Slide 19 text

リニューアル時における変更点 19 ● CMS機能面 ○ 予約投稿機能 ○ ページ複製による新規ページ作成機能 ○ ユーザー・権限管理機能 ○ HTML知識がなくてもページ作成可能なUI

Slide 20

Slide 20 text

リニューアル時における変更点 20 ● デザイン面 ○ イマドキなデザイン ○ レスポンシブデザイン ○ サイト構成の見直し

Slide 21

Slide 21 text

リニューアル時における変更点 21 ● デザイン面 ○ イマドキなデザイン ○ レスポンシブデザイン ○ サイト構成の見直し

Slide 22

Slide 22 text

旧サイトページからの リダイレクト問題 22

Slide 23

Slide 23 text

なぜリダイレクトが必要なのか 23 ● チラシ等の印刷物に旧サイトのイベントやキャンペーンページのURLを QRコード化して配ってしまっている ● SEOに強く、旧ページが検索HITする可能性が非常に高い ● 旧ページがニュースサイトやSNSで拡散されている可能性が非常に高い ● 旧ページから移設されたページのURLが全く異なる

Slide 24

Slide 24 text

なぜリダイレクトが必要なのか 24 ● チラシ等の印刷物に旧サイトのイベントやキャンペーンページのURLを QRコード化して配ってしまっている ● SEOに強く、旧ページが検索HITする可能性が非常に高い ● 旧ページがニュースサイトやSNSで拡散されている可能性が非常に高い ● 旧ページから移設されたページのURLが全く異なる

Slide 25

Slide 25 text

旧ページと新ページのURLが全く異なる 25 ● ほぼ全てのページがクエリパラメーター ○ /content/?id=xxxx の形式 ○ idの番号はページを作った順に割り当てられており法則性がない ○ リニューアル後はディレクトリ構成を明確化することになった ○ パラメーターはidだけではない ○ 正規表現化できない

Slide 26

Slide 26 text

旧ページと新ページのURLが全く異なる 26 ページタイトル 旧URL 新URL コープさっぽろの事業概要 /corporate/content/?cat=1 /about/overview/ 理事長からのご挨拶 /corporate/content/?cat=2 /about/greeting/ 広報誌「Cho-co-tto」(ちょこっと) TOP /content/?id=61 /about/pr-magazine/ お店でのポイントのため方・使い方 /content/?id=3225 /shopping/store/point/ 宅配「トドック」 /content/?id=11 /shopping/todok/ トヨヒコスイーツ /content/?id=3007 /item/toyohiko/ コープの共済 /content/?id=55 /service/cooperative/ 畑でレストラン /content/?id=1903 /event-activity/hatake-de-restrauant/ えほんがトドック /content/?id=1302 /children/book-gift/

Slide 27

Slide 27 text

旧ページと新ページのURLが全く異なる 27 ● 一部ディレクトリが移行対象外のためサブドメイン化して分離 ○ 子会社サイト ○ 採用サイト ○ カルチャースクールサイト ● 店舗情報画面をSaaSサービスへ移行した ○ サブドメインが変わる ○ 各店舗URLに店舗コードを振るようにした ○ スマホアプリと連動させる時に店舗コードの方が都合がよかった

Slide 28

Slide 28 text

旧ページと新ページのURLが全く異なる 28 ページタイトル 旧URL 新URL 北海道はまなす食品株式会社 /content/?id=46 https://h-hamanasu.jp/ 採用サイト /corporate/recruit/ https://recruit.sapporo.coop/ 新卒採用情報 /newgraduate/ https://newgraduate.sapporo.coop 店舗情報 /shop/ https://map.sapporo.coop/store/ 新さっぽろ店 店舗詳細 /shop/detail.html?no=117 https://map.sapporo.coop/store/detail/0103 生活文化教室 /culture/ https://life-culture.sapporo.coop

Slide 29

Slide 29 text

旧ページと新ページのURLが全く異なる 29 リダイレクトを 正規表現化できない

Slide 30

Slide 30 text

旧ページと新ページのURLが全く異なる 30 1対1での リダイレクトが必要

Slide 31

Slide 31 text

どのようにして リダイレクトさせるか 31

Slide 32

Slide 32 text

どのようにしてリダイレクトさせるか 32 ● CloudFrontエッジロケーション上で処理実行できるもの ○ CloudFront Functions ○ Lambda@Edge

Slide 33

Slide 33 text

このブログ記事が大変参考になりました 33 https://dev.classmethod.jp/articles/amazon-cloudfront-functions-release/

Slide 34

Slide 34 text

どのようにしてリダイレクトさせるか 34 ● 選定のポイント CloudFront Functions Lambda@Edge 同時実行数 上限なし デフォルトでMAX1000 必要に応じて上限緩和申請が必要 課金 100万回実行時 $0.1 1リクエストあたり$0.0000001 実行時間に対する課金なし 100万回実行時 $0.6 1リクエストあたり$0.0000006 実行時間に対する課金あり

Slide 35

Slide 35 text

どのようにしてリダイレクトさせるか 35 CloudFront Functions ええやん!

Slide 36

Slide 36 text

どのようにしてリダイレクトさせるか 36 リダイレクト対象 683ページ

Slide 37

Slide 37 text

どのようにしてリダイレクトさせるか 37

Slide 38

Slide 38 text

どのようにしてリダイレクトさせるか 38 1ms以内で683ページ分の リダイレクト処理!?

Slide 39

Slide 39 text

どのようにしてリダイレクトさせるか 39 CloudFront Functionsと CloudFront KeyValue Store

Slide 40

Slide 40 text

CloudFront Functions & KeyValue Store 40 CloudFront KeyValue Storeに登録するKeyとValue ページタイトル 旧URI=Key 新URL=Value コープさっぽろの事業概要 /corporate/content/?cat=1 https://www.sapporo.coop/about/overview/ えほんがトドック /content/?id=1302 https://www.sapporo.coop/children/book-gift/ お店でのポイントのため方・使い方 /content/?id=3225 https://www.sapporo.coop/shopping/store/poin t/ 宅配「トドック」 /content/?id=11 https://www.sapporo.coop/shopping/todok/ トヨヒコスイーツ /content/?id=3007 https://www.sapporo.coop/item/toyohiko/ 採用サイト /corporate/recruit/ https://recruit.sapporo.coop/ 店舗情報 /shop/ https://map.sapporo.coop/store/ 生活文化教室 /culture/ https://life-culture.sapporo.coop

Slide 41

Slide 41 text

CloudFront Functions & KeyValue Store 41 URI・クエリストリングと一致するKeyを検索 Value(リダイレクト先URL)が返ってくる Valueの値に301リダイレクト リクエストURIとクエリストリングをGET Keyがヒットしなかった場合はサイトTOPに飛ばす 当初考えていたフロー

Slide 42

Slide 42 text

CloudFront Functions & KeyValue Store 42 ● 直面した課題 ○ 使用しているクエリパラメーター名が複数ある ○ GoogleAnalytics用のクエリパラメーターは無視する ○ ディレクトリ宛等、クエリパラメーターがないパターンもある ○ S3オリジンのためディレクトリインデックス処理が必要 ○ for文とwhile文どっちがいいかな ○ asyncってなんやねん ○ constとletは何がちゃうねんん ○ thisってどれのことやねんんんんんんんん

Slide 43

Slide 43 text

CloudFront Functions & KeyValue Store 43 JavaScript よくわからん ShellScriptなら...

Slide 44

Slide 44 text

CloudFront Functions & KeyValue Store 44 アプリチームの人に 書いてもらおう わたくし、インフラエンジニアなんですよ...

Slide 45

Slide 45 text

CloudFront Functions & KeyValue Store 45 ペアプロをお願いした ワイ、インフラなんやけど...

Slide 46

Slide 46 text

ペアプロお願い!! 46 という話が舞い込んできた 話を聞こうか

Slide 47

Slide 47 text

アプリエンジニア観点での理解 47 CloudFront Functions, CloudFront KeyValue Store で 要件を満たす実装はできる? ● 基本的な新旧変換は KVS による変換で問題なさそう ○ サンプルにある通り。 ● 速度命 (<1ms) ○ Node.js ではなく、独自の JavaScript ランタイムっぽい。 ○ どのくらい機能が使えるか?

Slide 48

Slide 48 text

CloudFront Functions でできるのか? 48 CloudFront Functions の JavaScript ランタイム (2.0) https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html CloudFront Functions の JavaScript ランタイム環境は ECMAScript (ES) バージョン 5.1 に準拠しており、ES バージョン 6~12 の一部の機能をサポートしています。また、 ES 仕様に含まれない非標準メソッドも提供しています。 ざっとみた感じ、現代的な書き方で十分に書けそう。 (できればfor文を書かずにいきたい。)

Slide 49

Slide 49 text

ペアプロに向けて 49 木原の思う感じで 実装できそう ペアプロを進める上での課題は?

Slide 50

Slide 50 text

ペアプロを進める上での課題 50 ● クエリパラメータのところにロジックが必要 ○ KVSでマッチさせるURLには特定のクエリパラメータのみ使用。 ○ ここにロジックが必要。JSのメソッドチェーンの実装が思い浮かぶ。 ○ JavaScriptを書くアプリケーションエンジニアには問題ないレベル。 ● 担当者とのペアプロ ○ インフラエンジニア。 ○ 普段のチームも役割もスキルセットも異なる。 木原の思うコードを、「導きながら」書いてもらうのはハードルが高め。

Slide 51

Slide 51 text

ペアプロに向けて 51 どのように ペアプロを進めるか ペアプロで目指すことは何だろう

Slide 52

Slide 52 text

ペアプロをどう進めるか 52 ペアプロ = ペア・プログラミング どういう目的で「二人一組で」行うかを考えておく必要がある。 一般的なペアプロの目的 ● ペア対象者の教育 (技術を持つ人から、もう一人へ継承) ● 完成までの期間短縮 (双方の技術を同時に注ぎ込み課題解決) 付随効果で、複数人でコードをみながら開発するので、 開発したコードへの理解が双方に得られる。 今回は...?

Slide 53

Slide 53 text

ペアプロをやる前に目標設定 53 本番リリースまで時間がない →完成までの期間短縮(担当者の独力以上の速度で走り切る) ● ある程度コードスニペットをこちらから提示し、いい感じに配置していく形式で進める。 ● 多少の書き味の乱れには目を瞑る。 ● 機能を満たす形で完成させることが第一。 実施する中で自分が心がけたこと: 1. 教育ではないので、育成に重きを置きすぎない。 2. リリース直後に修正は発生するが一週間もすれば触らなくなるので、 メンテナンス性に重きを置きすぎない。 3. コード量も少なく内容も把握しているので、今後何かあっても木原が何とかできる。

Slide 54

Slide 54 text

ペアプロを実施 54 実際にやってみた

Slide 55

Slide 55 text

ペアプロを実施 55 書いてもらったコードの一部(クエリパラメータの処理) //リクエストのクエリストリング名を取得 const queryNameArray = Object.keys(event.request.querystring); // 使用するクエリパラメーター名 =id, cat, area, no, ID // 使用するクエリパラメタ名があるかを判定してパラメタ名を取得 const targetQueryKeys = Object.keys(event.request.querystring) .filter((item) => ['id', 'area', 'cat', 'no'].includes(item.toLowerCase()) ); // URI部分とパラメタ名=Valueを文字列連結 // クエリパラメタがEmptyもしくは使用しないパラメタの場合は URI部分のみを返す const targetKey = event.request.uri + String(targetQueryKeys.length > 0 ? '?' + targetQueryKeys .map((item) => `${item.toLowerCase()}=${event.request.querystring[item].value}`) : '');

Slide 56

Slide 56 text

ペアプロを実施 56 書いてもらったコードの一部(クエリパラメータの処理) //リクエストのクエリストリング名を取得 const queryNameArray = Object.keys(event.request.querystring); // 使用するクエリパラメーター名 =id, cat, area, no, ID // 使用するクエリパラメタ名があるかを判定してパラメタ名を取得 const targetQueryKeys = Object.keys(event.request.querystring) .filter((item) => ['id', 'area', 'cat', 'no'].includes(item.toLowerCase()) ); // URI部分とパラメタ名=Valueを文字列連結 // クエリパラメタがEmptyもしくは使用しないパラメタの場合は URI部分のみを返す const targetKey = event.request.uri + String(targetQueryKeys.length > 0 ? '?' + targetQueryKeys .map((item) => `${item.toLowerCase()}=${event.request.querystring[item].value}`) : ''); このパラメータもKVSのキーのマッ チングに使うため、 絞り込んで保持しておく必要あり。 マッチング用のURLの作成。

Slide 57

Slide 57 text

余談: 後から気づいたこと 57 CloudFront Functions の JavaScript ランタイムに、 クエリストリングを作るメソッドがあったよ。

Slide 58

Slide 58 text

58 ペアプロ完了!! 新たな JavaScript エンジニアの爆誕

Slide 59

Slide 59 text

本番切替 59

Slide 60

Slide 60 text

本番切替時に発生した問題 60 ● リダイレクト不要なアクセスが全部TOPにリダイレクト ● wwwなしからwwwありのドメインへのリダイレクト漏れ ● ディレクトリインデックス処理が店舗情報管理SaaS宛にも適用されていた ● リダイレクト対象の誤りや漏れがあった ● 別サブドメイン化したサイトへのリダイレクトがごっそり漏れていた ○ 全てTOPにリダイレクトするようにしていたが認識齟齬が発生していた ● セキュリティヘッダー処理の追加が必要なことに気がついた

Slide 61

Slide 61 text

本番切替時に発生した問題 61 ● リダイレクト不要なアクセスが全部TOPにリダイレクト ● wwwなしからwwwありのドメインへのリダイレクト漏れ ● ディレクトリインデックス処理が店舗情報管理SaaS宛にも適用されていた ● リダイレクト対象の誤りや漏れがあった ● 別サブドメイン化したサイトへのリダイレクトがごっそり漏れていた ○ 全てTOPにリダイレクトするようにしていたが認識齟齬が発生していた ● セキュリティヘッダー処理の追加が必要なことに気がついた 当日中に全て対応(木原さんに泣きついた

Slide 62

Slide 62 text

CloudFront KeyValue Storeで困ったこと 62 ● ファイルからのインポートは「初期投入」しかできない ○ 本番切替前は検証のために別ドメインにしていた ○ CloudFormationでドメインKVSリソースを削除・再作成で対応 ○ 検証中のKeyValue Pairs大量追加時もKVSリソース作り直しで対応

Slide 63

Slide 63 text

CloudFront KeyValue Storeで困ったこと 63 ● 本番稼働後はさすがにKeyValue Storeの作り直しができない ○ KeyValue Pairs追加のAWS CLI生成スプシを作った ○ スプレッドシートのCONCATENATE関数は便利

Slide 64

Slide 64 text

CloudFront KeyValue Storeで困ったこと 64 ● cloudfront-keyvaluestore put-keyコマンド ○ --if-matchオプションでKeyValue StoreのETagを指定する必要がある ○ KeyValue Pairsの追加が行われるたびにETagが変わる =CONCATENATE( "aws cloudfront-keyvaluestore put-key --key ",A2," --value ",B2," --kvs-arn arn:aws:cloudfront::123456789012:key-value-store/kvsid --if-match $( aws cloudfront-keyvaluestore describe-key-value-store --kvs-arn arn:aws:cloudfront::123456789012:key-value-store/kvsid --query 'ETag' --output text )" )

Slide 65

Slide 65 text

まとめ 65 ● 大量のリダイレクト処理はCloudFront KeyValue Storeを利用することで コードを簡素化できる ● KeyValue Pairsの大量投入時には工夫が必要 ● JavaScriptちょっとわかるようになった

Slide 66

Slide 66 text

66 最後にちょっと宣伝

Slide 67

Slide 67 text

トラフザメ ナヌカザメ 7/13土-14日 Cloud in the Camp 2024 札幌 Horippa https://connpass.com/event/315715/

Slide 68

Slide 68 text

ナヌカザメ 10/12土-13日 JAWS FESTA 2024 in 広島 https://jawsfesta2024.jaws-ug.jp/

Slide 69

Slide 69 text

ありがとうございました 69 https://www.wantedly.com/companies/company_7505384