[{"content":"紀錄我 2025 年從剛當完兵、入職 Cloud Engineer、備戰雙十一，到站上技術舞台與拿下鐵人賽冠軍的完整一年。\nSummary 2025 年，我從剛當完兵、第一份正職工作開始，一路走到雙十一、技術舞台，還有寫作賽事\n我在 Q1 成為 SHOPLINE Cloud Engineer，正式投入大規模電商系統的 Kubernetes 與 Networking 維運，涵蓋 EKS、CDN / Proxy Layer、TLS、以及高流量穩定性設計。入職後即被要求主講 EKS Deep Dive 內部分享，開始承擔雙十一備戰相關的系統責任。\nQ3～Q4，我參與 SHOPLINE 2025 雙十一 的系統備戰與當晚 shift，並見證 SHOPLINE 業績再創新高。\n在技術輸出與社群方面，我於 2025 年：\n於 2025 KubeSummit 代表 SHOPLINE 演講「從零打造 eBPF CNI Plugin：揭秘 Cilium 封包處理核心原理」該場次為 全場滿意度最高演講，內容深入底層從 Cilium 原始碼與論文出發，實作 Prototype 解構封包處理流程，並得到 Cilium 官方轉發 首次參賽 iThome 鐵人賽（Cloud Native 組）獲得冠軍，完成「30 天深入淺出 Cilium：從入門到實戰」系列，深入淺出教學 Cilium 與實戰經驗 成為 AWS Community Builder（Serverless） 並持續以 Mentor 身份參與 AWS Educate Cloud Ambassador，累積 10+ 場技術與職涯培訓課程 整體而言，2025 是我把 Networking / Kubernetes / Cloud Infra 從「深度學習」推進到「可被驗證輸出」的一年，無論是在大流量實戰、底層原理拆解，或對外技術傳播上，都開始累積出一條穩定的專業方向\n怎麼比較少在發部落格文章？ 一大部分原因是閒暇時間比較少，想做的事情、和自己的目標非常多，如果有讀者對我成長歷程有興趣，建議可以直接去看 Notion，我每天都會在 Notion 寫文章！\n有比較多閒暇時間時就會來更新一下，像是現在我剛好休蠻多假就趁這時間來更新\n2025 年 Q1 進入戰場 1 月｜退伍後，開始積極找第一份正職 24 年 12 月底退伍後，1 月對我來說算是一個正式切換狀態的時間點。\n老實說，男生大學畢業後大多都會卡在等兵單這一關，我身邊也不少朋友跟我說，如果還沒當完兵就去找工作，實際上會比較難。所以在退伍後，我才真正開始認真投入找工作。\n那段時間，一方面我持續在 AWS Educate Cloud Ambassador 裡面做事，另一方面也把大部分空檔拿來讀書，目標很明確，就是把 AWS Advanced Networking – Specialty 這張證照考下來。\n2 月｜考到了 ANS 證照，找工作比想像中慢 過年前，成功考到 AWS 專家級證照 - AWS Certified Advanced Networking – Specialty\n這張證照準備的時間其實不短，題目又長又複雜，但因為本來就很喜歡 Networking，整個準備過程對我來說反而是快樂的，也確實把很多網路相關的觀念再打磨了一次。\n找工作的部分，進度就沒那麼快了…\n並不是沒有工作機會，反而是我很清楚自己想要什麼，也因此變得很挑。我不是在找一份「可以做的工作」，而是在找一個我願意長期投入的方向。如果我自己都說服不了自己這份工作值得投入，那就算拿到 offer，我也不會開心。\nI’m not looking for a job. I’m looking for a career.\n所以 1、2 月那段時間，我幾乎每天都在看 104、LinkedIn、Cake，但真正投出去的職缺只有一個（不是 SHOPLINE）。隨著時間過去，心裡的焦慮感也持續上升\n直到有一天，我在 LinkedIn 上看到 Andy Chung （是我現在的部門主管）發的一篇徵才文\n那份 Job Description 我一行一行看完，裡面寫的內容，剛好完整對齊我想投入的方向。我那時候還把貼文轉傳到朋友群，直接跟朋友說，這個職缺真的超合我想要走的方向，公司文化也很契合，一定把握機會。後來我鼓起勇氣私訊 Andy，小聊了一下，沒想到就這樣拿到了面試機會。\n現在回頭看，那是 2025 年一個非常關鍵的轉折點\n很感謝 Andy 願意給我這寶貴的面試機會\n3 月｜我成為 SHOPLINE Cloud Engineer 了 收到面試邀請後，我的心情其實很複雜… 當下除了開心之外、剩下就是超級無敵緊張，我不知道怎麼形容我這「超級無敵」，整個緊張成分包含：\n這是我第一場正職工作的面試，過往的面試經驗只有伊雲谷 Intern、AWS Educate Cloud Ambassador 是等了很久才看到一個很符合自己期望的職缺，「我很害怕錯失這個機會」 我的資歷尚淺，對於大型系統維運還沒有很豐富的經驗，我很害怕「技術面試什麼東西都答不出來」 簡單來說，有一種剛踏出新手村就要挑戰魔王副本的感覺。\n面試細節就不在這裡細談 XD，如果有讀者對 SHOPLINE 職缺有興趣可以 Linkedin 找我聊聊\n最後的結果是，我順利成為 SHOPLINE 的一份子，加入 Cloud Team 裡的 Platform 小組，主要專注在 Infra, Networking 的領域，需要熟悉領域的技術：\nCDN / Proxy Layer SSL/TLS Certificates EKS (在 K8s 裡面的組件很多，我們負責那些偏 Infra 的組件) 其他任何 Networking 相關的技術 整個 3 月幾乎都在高速學習，其中當時我有被要求準備內部的 EKS 分享，必須自己 Deep Dive 底層細節，再把內容整理給部門同事聽。這段時間的學習密度非常高，我從 Mao 和 Daniel 前輩的身上學到超級多，這也讓我很快進入工作狀態\n順帶補充，SHOPLINE 有拍一個短影音叫做「職場感謝祭」，我有幫忙拍，其中我最感謝的人就是 Mao Wang\n3 月｜成為 AWS Community Builder 同一個月，我也收到 AWS Community Builder 的通知：我成為 AWS Community Builder 了！\n我主要的貢獻是撰寫技術文章、開發 Serverless 應用和舉辦 Workshops，我的技術領域除了專注在 Networking, Container 之外，還有一個我也很熱愛的領域是 Serverless！我有數個 Side Project 都是用 AWS 的各個 Serverless 服務 Build 出來的，很榮幸我能在 AWS Serverless 領域也能貢獻一份心力。\n詳細 Contributions 和 Projects 可以參考： AWS Community Builder - My AWS Contributions 另外，最近 2026 年的 AWS Community Builders Program 也要在 1 月份左右開放申請了，如果也有人想要申請成為 AWS Community Builder 過程有任何疑問也歡迎 Linkedin 找我聊聊\n另外提到「貢獻」，其實我目前仍以 Mentor 的身份參與 AWS Educate Cloud Ambassador，負責培訓現任大使。過去的一屆舉辦了 8+ 場課程，從那一屆大使身上得到回饋滿意度是 5/5，領域從 Serverless、Container、Networking、Project Management 到職涯分享都有，現在也持續在這個組織裡陪著新一屆的大使成長，培訓課程也持續增加中目前總累積已經 10+ 場課程了。\n也希望如果讀者還是學生，不妨之後開放招募時也來申請看看，如果對於 AWS Educate Cloud Ambassador 這個組織有什麼問題我都很樂意 Linkedin 上面聊喔\n2025 年 Q2 技術實力突飛猛進 4 月 ~ 6 月｜站穩角色，試用期通過 我是 3 月入職的，所以 4 到 6 月對我來說，就是全力把「Cloud Engineer 這個角色」站穩的階段\n入職後，我給自己設定的第一個明確目標其實很單純：試用期要通過。這段時間我的技術能力確實成長非常多，一方面是工作內容本身就有足夠的深度，另一方面也很大程度來自於身邊的同事真的願意帶人。\n而 SHOPLINE 作為目前台港市占率最高的 SaaS 線上開店平台（可以簡單理解成亞洲版 Shopify），對我來說有很多挑戰讓我很興奮，像是：我們組所負責的 OpenResty 就是在最前面扛大流量的組件、管理上萬張的 SSL/TLS Certificates、防禦 DDoS …\n我自己其實也很在意一件事，就是實際入職後，這個職位是不是和當初投遞時的想像一致。畢竟身邊也聽過不少例子，職缺寫得很好看，但實際進去卻完全不是那回事。\n以我自己的經驗來說，SHOPLINE 的 Cloud Engineer 和我原本期待的角色是完全對得起來的。我的日常工作確實圍繞在 Kubernetes、Networking 這些核心技術上，也能實際接觸到系統穩定性與 infra 層面的問題，實際日常工作中有很多問題都是要非常深入底層，而不是只停留在表層操作。\n整個 4 到 6 月，我一邊熟悉既有系統，一邊補齊自己在大型系統維運上的不足。很多下班後的時間，其實也都拿來把工作中遇到的技術問題再深入研究，不是在加班 XD 是因為那些本來就是我自己感興趣的領域。\n最終在 6 月，我順利通過試用期。對我來說，這不只是聘雇流程上的一個節點，而是一個很實際的訊號：我已經能夠勝任這個角色的日常責任了。\n2025 年 Q3 備戰雙十一 7 月 ~ 9 月 通過試用期後，7 到 9 月我的工作重心開始更明確地放在備戰雙十一。\n在電商產業裡，雙十一其實很像一個每年都會遇到的固定大考日。公司會有很清楚的共同目標，希望在營收創新高的同時，技術團隊也必須確保整個系統能夠穩定撐住流量。這段期間，系統會反覆進行各種壓力測試，也會一輪一輪檢視不同環節的風險。\n工作的實際細節我不打算在這篇文章裡展開，但對我來說，這段時間最大的挑戰其實不是某一個特定技術，而是開始反覆思考一個問題：如果雙十一真的倒站了，我當下可以做什麼？\n難的地方在於，系統本身非常龐大。當真的出問題時，不一定能第一時間就定位到問題點；即使找到了問題，接下來要思考的也不是「為什麼會壞」，而是「現在能不能先止血、怎麼緩解影響」。這種思考方式，和過去只專注在單一元件或功能的問題，其實是完全不同的層級。\n也因為備戰雙十一的關係，我開始更有意識地從「整體系統」的角度去看事情，而不是只關注自己負責的那一小塊。\n關於 SHOPLINE 怎麼備戰雙十一，推薦可以去看 SHOPLINE 的這個短影音，裡面提到工程團隊、AM 顧問團隊、Admin 團隊各自如何備戰\n同一段時間，我們也開始面試第 8 屆的 AWS Educate Cloud Ambassador。這對我來說算是一個蠻有趣的對比：一邊是在高壓的商業實戰場景，另一邊則是看到學生世代的技術趨勢。\n在面試過程中，很明顯可以感受到 AI 已經是許多學生的日常工具，不只是會用，而是懂得怎麼拿來輔助學習與實作。有些作品甚至會讓人一瞬間忘記這是學生做出來的。對我來說，這也提醒了自己，技術環境變化的速度其實一直都很快，不能停在既有的舒適圈。\n2025 年 Q4 10 月｜KubeSummit 全場滿意度最高講者、Cilium 官方轉發 在 2025 KubeSummit，我代表 SHOPLINE 講了一場演講，主題是「 從零打造eBPF CNI Plugin：揭秘Cilium封包處理核心原理」\n靈感來自 Liz Rice 在 2018 年 GOTO Conference 的經典演講「Containers From Scratch」，Liz Rice 透過 Golang 從零實作出一個容器，深入剖析其底層原理。\n所以我也想把這精神延伸至 Cilium 領域——以 From Scratch 的方式打造一個 Cilium Prototype，帶領大家從底層角度「揭秘 Cilium 的封包處理核心原理」。\n準備這場演講花了很長的時間，主要是因為我必須先真的「理解 Cilium」，而不是停在使用層。那段時間我花了大量時間看 Cilium 的原始碼，補齊底層網路相關的知識，也去讀了一些論文，確保自己不是只是在拼湊概念，而是真的知道每一段設計背後在解什麼問題。\n最終，這場演講成為 2025 KubeSummit 全場滿意度最高的一場演講\n我也很感謝每一位聽眾的回饋，其中有一個聽眾回饋寫得非常長、也非常具體，讓我知道這場分享真的有幫助到人深入底層學習 Cilium 的人\n另外，也很開心這場演講的 Linkedin 貼文 有被 Cilium 官方轉發，對我來說算是一個意想不到、很珍貴的肯定\n11 月｜ 雙十一購物節 SHOPLINE 業績創新高 11 月最重大也最精彩的活動莫過於雙十一購物節\n11/10 晚上，SHOPLINE 內部稱為「雙十一之夜」。這一天公司會特別調整排班，部分夥伴會從下午一路工作到 11/11 凌晨，而我正好被排在雙十一之夜的那個 shift。老實說，心情是有點興奮，但也帶著一點緊張和恐懼 XD 因為這是我第一次在這種等級的流量場景，站在第一線。\n👆 推薦大家可以去 Instagram 找 shopline_careers，然後查看「工作日常」精選動態，裡面有雙十一之夜的小影片，上面圖片是從精選動態截圖的\n雙十一之夜的核心目標其實只有一個：確保系統在 11/10 跨到 11/11 的那個瞬間能夠穩住。\n所以雙十一之夜這天會有一段時間是要等到流量到高峰，這段等待期間 SHOPLINE 有準備晚餐、飲料、安排團康遊戲（有獎金）、宵夜和各種補給品（例如：提神飲料、雞精、零食），半夜沒有大眾運輸所以這天也能搭計程車回家然後向公司報帳\n而我找 Cloud Team 的同事組一隊去玩團康遊戲，今年的遊戲是「中文怪物」和「猜人、猜物」：\n中文怪物：投影幕上會出現一些生難字，我們要寫出注音，必須全對才給分，例如：題目是蛤蜊，那你就要在答題紙上面寫上「ㄍㄜˊ ㄌㄧˊ」，全部正確才得分 猜人、猜物：投影幕上會出現圖片，例如：會出現某個人物（可能會有 SHOPLINE 同事 XD）或是某個物品，要講出全名才得分 而我們這組有學霸（不是我）、也有在 SHOPLINE 工作超久的同事（如果沒記錯印象中已經待了 9 年），我們這組竟然拿了最高分，取得團康遊戲的第一名，拿到了獎金，好開心 XD\n後續就是要等到 11/10 23:00 左右，SHOPLINE 全員就會開始進入備戰狀態，每個人都會就位，像是 Cloud Team、System Architect、PMO 還有高階主管會被分配在一間戰情室，確保系統出現異常時能夠直接在戰情室內迅速溝通並緩解線上狀況。\n對我來說，這一晚真正的挑戰並不是某一個具體技術，而是「在高壓環境下，如何保持判斷清楚」。如果系統真的出問題，重點不是立刻找出所有原因，而是先判斷影響範圍、優先止血，確保使用者體驗不被進一步放大影響。\n而今年雙十一之夜我們很順利的度過！而讓我們更開心的另一件事情是整個雙十一檔期 SHOPLINE 的業績又創新高了！\n如果對 SHOPLINE 2025 雙十一業績的 insight 有興趣，可以看這篇公開文章\n11 月｜東吳大學社團演講 接續雙十一購物節，我也很開心受邀到東吳大學的人工智慧應用社擔任講師，我在這裡的講的主題是「AWS x n8n 實戰工作坊：打造 LINE AI Bot 助理」，最終成品是做出一個 LINE AI Bot，我可以對這個 Bot 詢問像是「活動報名狀況」，Bot 會自己去讀取 Google Sheets 得知活動報名狀況，然後產生 Insights，接著再產生漂亮的圖表\n我拆成兩堂社課去講：\n11/19：n8n 基本教學、AWS CloudFormation 教學、 LINE 整合 n8n 11/26：賦予 LINE Bot AI 的能力，像是教學了 AWS Bedrock、Google 串接、AWS Serverless 服務、AI Agent 實作 這次對我來說比較有挑戰的地方在於，現場的學生背景差異其實很大，有些人是第一次接觸雲端與自動化工具。因此，我刻意把很多原本看起來很複雜的概念，拆成一步一步能跟得上的流程，也把很多複雜的概念用更白話的方式解說，並根據學生的狀況臨時穿插一些補充教學內容，而不是直接丟名詞。\n最終這場社團演講，我拿到了 4.8/5 的滿意度！11/26 課堂結束後，就有不少學生覺得我教得很豐富很清楚，把很多複雜的概念講得簡單易懂，也讓不少學生對 AWS 技術產生興趣，當下就有看到學生去報名 AWS Educate Cloud Ambassador 舉辦的證照陪跑計畫，每次演講完看到這種反饋都很有成就感，這也是我很享受擔任講者以及與人交流技術的原因之一\n12 月｜ iThome 鐵人賽 Cloud Native 冠軍 在台灣 IT 圈有一個非常有名、也很有代表性的 技術寫作挑戰賽，叫做「iThome 鐵人賽」，由 iThome 主辦，參加這個比賽的選手必須連續 30 天，每天寫一篇技術文章並成功發表，只要有一天沒發表就等同失敗！\n而每年賽制中都針對不同技術分領域開一些競賽，參賽時你要選你要比哪一個競賽主題，而寫出來的技術文章也必須切合主題，簡單舉幾個 2025 年有的競賽主題：\nAI \u0026amp; Data Build on AWS Cloud Native（這是我參加的主題） Rust …. 這是我第一次參賽 iThome 鐵人賽，報名的是 Cloud Native 組，系列是「30 天深入淺出 Cilium ：從入門到實戰」\n其實這次參賽的決定非常臨時，我是在報名截止前一天才送出報名，沒有庫存、沒有提前規劃，幾乎是把自己逼到沒有退路。而會想要參加的其中一個原因是我當時正好在準備 KubeSummit 的演講，索性就把那段時間對 Cilium 的學習與理解完整地記錄下來。\n另一個原因是受到前輩 Mao 的影響，Mao 之前也有參賽鐵人賽並且著有一本書「那些文件沒有告訴你的AWS EKS：解析Kubernetes背後的奧秘」，我自己也是這本書的讀者，所以我也希望自己能跟上前輩的腳步 — 「拿下鐵人賽獎項、在自己的人生中出版一本書」\n而臨時決定參加會怎樣？代價很直接\n👉 每天都被鐵人賽追殺…\n因為我沒有任何「庫存」，而且我還是最後一天才報名，所以我完全沒有喘息的空間，甚至團隊在 Team building 時得跟別人借電腦發文… 這也導致我那陣子每天都凌晨 3, 4 點才睡覺，而且得盡可能放棄任何玩樂，為得就是能趕緊多產出內容我就能多一點喘息空間，但是現在回想起來真是瘋狂，而且我很謝謝當時的自己堅持下去\n在這個過程中，我也非常感謝 Mao 與 Andy 的支持。除了鼓勵之外，Mao 也給了我很多實際的建議，像是 Mao 告訴我哪些參賽作品是很好的模範，怎樣的排版、筆風和描述是很值得學習的等等… 以及 Mao 在工作上、技術上 Deep Dive 的精神讓我鑽研 Cilium 技術時，也自然地深入底層探究原理，而不是表面的「會用」、「知道按下按鈕什麼就會怎樣跑」\n最終，我獲得了 2025 年 iThome 鐵人賽 Cloud Native 組 冠軍。\n最後也要感謝評審 楊承昊 (ChengHao Yang) 的時間與對我作品的賞識，真心感謝\n回顧 2025 這一整年，我以肉眼可見的速度在成長，期望我自己這份熱情可以一直持續下去，我希望自己的正面影響力能持續擴大，除了自己強、也要讓別人強，期待 2026 年能有更多精彩的故事可以分享給大家！\n","permalink":"https://shiun.me/blog/2025-recap/","summary":"紀錄我 2025 年從剛當完兵、入職 Cloud Engineer、備戰雙十一，到站上技術舞台與拿下鐵人賽冠軍的完整一年。\nSummary 2025 年，我從剛當完兵、第一份正職工作開始，一路走到雙十一、技術舞台，還有寫作賽事\n我在 Q1 成為 SHOPLINE Cloud Engineer，正式投入大規模電商系統的 Kubernetes 與 Networking 維運，涵蓋 EKS、CDN / Proxy Layer、TLS、以及高流量穩定性設計。入職後即被要求主講 EKS Deep Dive 內部分享，開始承擔雙十一備戰相關的系統責任。\nQ3～Q4，我參與 SHOPLINE 2025 雙十一 的系統備戰與當晚 shift，並見證 SHOPLINE 業績再創新高。\n在技術輸出與社群方面，我於 2025 年：\n於 2025 KubeSummit 代表 SHOPLINE 演講「從零打造 eBPF CNI Plugin：揭秘 Cilium 封包處理核心原理」該場次為 全場滿意度最高演講，內容深入底層從 Cilium 原始碼與論文出發，實作 Prototype 解構封包處理流程，並得到 Cilium 官方轉發 首次參賽 iThome 鐵人賽（Cloud Native 組）獲得冠軍，完成「30 天深入淺出 Cilium：從入門到實戰」系列，深入淺出教學 Cilium 與實戰經驗 成為 AWS Community Builder（Serverless） 並持續以 Mentor 身份參與 AWS Educate Cloud Ambassador，累積 10+ 場技術與職涯培訓課程 整體而言，2025 是我把 Networking / Kubernetes / Cloud Infra 從「深度學習」推進到「可被驗證輸出」的一年，無論是在大流量實戰、底層原理拆解，或對外技術傳播上，都開始累積出一條穩定的專業方向","title":"回顧我的 2025 年成長之旅"},{"content":" 在 我的 Notion Kubernetes 學習筆記 - Jan 13, 2025 中，我透過 CKA 的課程學習到 kubectl apply 背後的原理，而我知道 kubectl apply 背後有三個東西來決定它如何變更資源：\nConfiguration file：配置文件，代表使用者的最新意圖。 Last applied configuration：上次使用 kubectl apply 時記錄的配置，用於追蹤變更。 Live configuration：目前 Kubernetes 叢集中的實際配置狀態。 以下引用 Kubernetes 官方文件的說明：\nWhen kubectl apply updates the live configuration for an object, it does so by sending a patch request to the API server. The patch defines updates scoped to specific fields of the live object configuration. The kubectl apply command calculates this patch request using the configuration file, the live configuration, and the last-applied-configuration annotation stored in the live configuration.\n我在學習時就很納悶也很疑惑：為什麼不直接比較 Configuration file 和 Live configuration 就好？反正 Configuration file 寫什麼就是絕對真理，Live configuration 照著聲明的期望配置去實現就好，為何這中間還需要多一個 Last applied configuration？\nTL;DR Last Applied Configuration 是 kubectl apply 判斷變更意圖的關鍵：\n如果值之前是你設置的（在 Last Applied Configuration 中）但現在刪除，會刪除 Live Configuration 中的值。 如果值不是你設置的（不在 Last Applied Configuration 中，可能是其他用戶設置的），則保留 Live Configuration 中的值。 這樣設計的目的是：\n尊重用戶的變更意圖：保證新配置與 Live Configuration 的同步。 避免誤刪其他系統或用戶配置的值：確保 Live Configuration 的穩定性和完整性。 建議可以搜尋關鍵字：Three-way merge，就能理解背後的意涵\n為什麼需要三個資料來源？ 關鍵字：Three-way merge\n為了解開我對這裡的迷惑，當初我問了很多次 ChatGPT 4o ，但他給的解釋我都不是很滿意。\n回到正題！其實，這與 欄位刪除意圖的判斷 (Determining the deletion intent of a field) 有關。\n假設我們只比較 Configuration file 與 Live configuration，當某個欄位在本地配置中不存在時，Kubernetes 無法確定：\n該欄位是使用者從配置文件中有意刪除，應從 Live configuration 中清除。 該欄位是透過其他方式（如手動修改或其他工具）新增至 Live configuration，應予以保留。 這就是為什麼 Last applied configuration 非常重要，它是判斷欄位刪除意圖的核心依據。\n後來看到 Udemy 課程底下的問與答有一位網友 Alegandro 解釋得很不錯，以下我整理成中文，後面會附上原始引文。 Alegandro 在 Udemy 問與答中的解釋 kubectl apply 如何處理變更：\n配置文件的值永遠優先於 Live Configuration。 如果值存在於配置文件：覆蓋 Live Configuration 中的值。 如果值是新增加的：在 Live Configuration 中新增該值。 如果值不存在於配置文件，但存在於 Live Configuration： 如果值在 Last applied configuration 中，表示用戶有意刪除該值，應從 Live Configuration 中清除。 如果值不在 Last applied configuration 中，表示該值是其他方式新增的，應予以保留。 以下為原始引文：\nAlegandro:\nThe values of the configuration file are always going to \u0026ldquo;win\u0026rdquo; over the live configuration.\nIf the value exists, it gets replaced. If the value is new, it gets created If the value doesn\u0026rsquo;t exists on the configuration file but exists on the live configuration, there can be 2 reasons:\nThe value was removed from the configuration file. In this case, you want to remove the value from the live configuration. The value was added to the live configuration not using the configuration file. In this case, you don\u0026rsquo;t want to remove the value of the live configuration. To know which of the 2 options was the reason of the change, you need the \u0026ldquo;last configuration applied\u0026rdquo;. This way:\nIf the value was on the last configuration applied, it means that the value was removed from the configuration file, so remove it from the live configuration. If the value was not on the last configuration applied, it means that the live configuration was created by other means, so don\u0026rsquo;t remove the value. I think that the intention of this is to respect the intention of the changes, if it\u0026rsquo;s in the configuration file, respect the value, if it\u0026rsquo;s not, it can be because it was deleted from the configuration or because it was created in another way. In the last case, don\u0026rsquo;t remove what was created by other means.\n總結 為什麼需要 Last Applied Configuration，核心思想就是尊重變更意圖，正確處理欄位的更新和刪除：\n它是判斷欄位刪除意圖的依據。 沒有它，Kubernetes 無法區分「欄位被刪除」和「欄位是由其他系統動態生成」這兩種情況。 相關連結 [CKA] Kubernetes 學習筆記 - Core Concepts - Shiun Notion Site Declarative Management of Kubernetes Objects Using Configuration Files Why is a 3-way merge advantageous over a 2-way merge? ","permalink":"https://shiun.me/blog/what-is-the-purpose-of-last-applied-configuration-in-kubectl-apply/","summary":"在 我的 Notion Kubernetes 學習筆記 - Jan 13, 2025 中，我透過 CKA 的課程學習到 kubectl apply 背後的原理，而我知道 kubectl apply 背後有三個東西來決定它如何變更資源：\nConfiguration file：配置文件，代表使用者的最新意圖。 Last applied configuration：上次使用 kubectl apply 時記錄的配置，用於追蹤變更。 Live configuration：目前 Kubernetes 叢集中的實際配置狀態。 以下引用 Kubernetes 官方文件的說明：\nWhen kubectl apply updates the live configuration for an object, it does so by sending a patch request to the API server. The patch defines updates scoped to specific fields of the live object configuration. The kubectl apply command calculates this patch request using the configuration file, the live configuration, and the last-applied-configuration annotation stored in the live configuration.","title":"Kubernetes 常見問題：kubectl apply 的 Last applied configuration 用途是什麼？"},{"content":"在前一篇教學中 如何將現有 NLB IPv4-only 架構升級為 Dual-stack，我們已經配置好一個支援 Dual-stack 的 NLB 了。\n現在假設有一個需求：「客戶需要調用我們的 API，且他們也使用 AWS。」有沒有辦法不經過 Internet，而是直接透過 AWS 的骨幹網路 (Backbone)，將流量送到我們的 NLB？同時避免使用 VPC Peering 或 Transit Gateway，確保網路不完全打通，以降低潛在風險，例如資料洩漏或不必要的安全隱患。\n答案就是 PrivateLink\n什麼是 PrivateLink？ AWS PrivateLink 是一種安全的網路技術，允許服務提供者 (Service Provider) 將他們的服務透過 VPC Endpoint 暴露給使用者 (Service Consumer)，而不需要將流量經過公網 (Internet)。它能確保所有流量都在 AWS 的骨幹網路內部傳輸，提供高安全性、低延遲的解決方案。\nPrivateLink 的核心特點:\n避免暴露服務到公網： Service Provider 的服務不需要有 Public IP，Consumer 可以透過 Private IP 訪問這些服務。 簡化網路架構： 無需設定 VPC Peering、Transit Gateway，所以不用擔心整個網路打通的安全風險。 跨帳號支持： Service Provider 和 Consumer 可以位於不同 AWS 帳號，甚至不同 AWS 組織。 多 Region 支持： PrivateLink 現在也支援跨區域的流量傳輸 (需額外配置)。(Reference) 可以自定義的 Private DNS name： Consumer 可以直接使用 Service Provider 提供的 Private DNS Name 調用服務。 Terminology Service Consumer: 使用服務的一方。Consumer 會在自己的 VPC 中建立 VPC Interface Endpoint 來連接到 Provider 的 Endpoint service。Consumer 必須等待 Provider 接受連接請求後，才能開始使用服務。透過這種方式，Consumer 可以安全地存取 Provider 的服務，而無需經過公有網路。 Endpoint: VPC Interface Endpoint (VPCE) 是一個彈性網路介面，具有私有 IP 位址。它作為進入 AWS 服務的進入點，讓 VPC 中的資源可以私密地存取這些服務。在 PrivateLink 架構中，這是 Consumer 端建立的元件。 Service Provider: 提供服務的一方。Provider 需要建立 Endpoint service 並將其與 NLB 或是 GWLB 關聯，然後可以選擇性地允許哪些 AWS 帳戶可以連接到此服務。Provider 負責接受或拒絕來自 Consumer 的連接請求。 Endpoint service: Endpoint service 是由 Service Provider 建立的服務，可以與 Network Load Balancer (NLB) 或是 Gateway Load Balancer (GWLB) 關聯。這個服務允許其他 AWS 帳戶 (Consumer) 通過 VPC Interface Endpoint 連接到 Provider 的服務。 Section 1: 配置 PrivateLink Step 1: [Provider] 創建 Endpoint service 在 Section 1 - Step 1，我們要扮演 Service Provider，我們是提供 Service 的人。\n進入 VPC 頁面 \u0026gt; 左側欄 Endpoint services:\n點擊 Create endpoint service 進入 Create endpoint service 頁面後，請依照以下內容進行配置：\nLoad balancer type: Network Available load balancers: 選擇你的 NLB 其他部分可以保持預設\n創建好後，複製 Service name\nStep 2: [Consumer] 創建 VPC Interface Endpoint (VPCE) 現在我們的身份切換至 Consumer\n身為 Consumer，我們需要建立一個 VPC Interface Endpoint (VPCE)，透過 VPCE 存取到 Service Provider 的服務。\n進入 VPC 頁面 \u0026gt; 左側欄 Endpoints:\n點擊 Create endpoint 創建 VPC Interface Endpoint，請依照以下內容進行配置：\nType: Endpoint services that use NLBs and GWLBs Service name: 貼上剛才在 Service Provider 那邊所複製的 Service name 創建好 VPC Interface Endpoint 之後，身為 Consumer，其實還不能馬上存取 Service，必須等待 Provider 接受你的連線請求才可以使用。\nStep 3: [Provider] 接受 Connection Request 身份切回 Service Provider\n回到 VPC 頁面 \u0026gt; 左側欄 Endpoint services:\n勾選 Endpoint service 進入 Endpoint connections 頁籤 勾選 Endpoint connection 展開 Actions menu 點擊 Accept endpoint connection request Step 4: [Consumer] 測試是否可以透過 VPC Interface Endpoint 調用到 NLB 身份切回 Consumer\n剛才 Provider 已經接受連線了，回到 VPC \u0026gt; 左側欄 Endpoint 頁面，檢查一下目前 Endpoint 的 Status：\n他會停在 Pending 狀態一下下，大概 1~2 分鐘，等到變成 Available 就 OK 囉！如下圖紅框處所示：\n現在 Endpoint Status 已經是 Available 狀態，要測試是否能透過 VPC Interface Endpoint 調用到 Service Provider 的 NLB，首先要先複製 VPC Interface Endpoint 的 DNS name\n你會注意到 DNS names 提供了兩個 Domain name，下面沒有被紅框框起來的是 Zonal DNS name，使用 Zonal DNS name 可以保證你的流量一定打到你要的 AZ 關於 AZ 這方面有一個注意事項，可以參考我的 Notion 筆記: 你知道嗎？此 AZ 非彼 AZ\n我現在在 Consumer AWS 帳號，透過一台跳板機 SSH 連線到 Private subnet 裡面的一台 Instance\n成功連線後，輸入以下指令來存取 VPCE，請務必注意： curl 後面實際值換成你剛才複製的 VPC Interface Endpoint DNS name：\n$ curl vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 注意： 如果發現沒有得到任何 Response，請記得檢查 Security Group 設定，確保 VPC Interface Endpoint 的 SG Inbound rule 允許 EC2 流量進入，詳細請見：Troubleshooting - 配置 VPC Interface Endpoint 的 SG。\n透過上面的輸出，這樣就達成「完全不透過 Internet 就打流量到 NLB」的架構囉！流量都是在 AWS Backbone 網路中內傳輸。\n注意： VPC Interface Endpoint 和 我連線進去的 EC2 Instance 都放在 Private subnet，我也沒配置 NAT 或是 EIGW，所以他們是不可能有辦法連到網路的\n以下簡單證明沒有連網能力，我先 ping google.com 再 curl VPC Interface Endpoint\n恭喜！我們已經成功配置好 PrivateLink 囉！\nSection 2: Dual-stack 在 Section 2，將帶大家實現 Dual-stack PrivateLink，讓 Consumer 以及 Provider，同時都可以支持 IPv4 和 IPv6\nStep 1: [Provider] 為 Endpoint Service 開啟 IPv6 Support 目前身份是 Service Provider\n因為當初創建 Endpoint Service 時，沒勾選 IPv6 support，可以觀察下圖的 Supported IP address type 中僅顯示 IPv4 ，所以用 IPv6 流量打過去會無法解析：\n以下範例是以 Consumer 身份連線進去 Private Instance，利用 curl -6 調用 VPC Interface Endpoint 所得到的輸出：\n$curl -6 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com curl: (6) Could not resolve host: vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com OK，上面只是做個範例，讓大家知道現在真的無法用 IPv6 Client 去存取 Endpoint，讓我們再次回到 Provider AWS 帳號，進入 VPC 頁面 \u0026gt; 左側欄 Endpoint services:\n選取 Endpoint service 展開 Actions menu 點擊 Modify supported IP address types 在 Supported IP address types 下方選項把 IPv6 也勾選起來：\nStep 2: [Consumer] 使 VPC Interface Endpoint 支持 Dual-stack 目前身份是 Consumer\n接下來我們也要確保 Consumer 這裡的 VPC Interface Endpoint 也支持 Dual-stack，因此要記住！並不是 Provider 將 Supported IP address types 下的 IPv4, IPv6 都啟用後 Consumer 這裡就可以什麼事都不用做就可以用 IPv6 Client 去調用喔，畢竟在 Consumer 這裡，面對的是自己的 “VPC Interface Endpoint”\n進入 VPC \u0026gt; 左側欄 Endpoints\n選取 Endpoint 展開 Actions menu 點擊 Modify endpoint settings 進入 Modify endpoint settings 頁面：\nIP address type: Dualstack DNS options: Dualstack Save 之後 Endpoint 會進入 Pending 狀態，要等 1~2 分鐘直到它變成 Available 狀態\n當 Endpoint 狀態變成 Available 後，SSH 連線 EC2 Instance 再來測試一次吧：\n$ curl -6 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 補充資訊：我這邊剛好沒 Clear CLI，所以可以看到設定成 Dualstack 的前後變化，一開始還無法解析成功，經過上面的重新設定，就成功使用 IPv6 Client 去打流量到 VPC Interface Endpoint 碰到 NLB 唷 恭喜，我們這樣就完成 Dual-stack PrivateLink!\nSection 3: Private DNS name Private DNS name 是一個很方便的功能，如果你是 Service Provider，想要配置 Private DNS Name 你必須擁有域名，例如: Private DNS name 設定為 example.com 的話，經過 DNS 驗證之後，Consumer 端之後就可以直接用 example.com 來存取這個 Endpoint Service\n官方文件： Manage DNS names for VPC endpoint services - Amazon Virtual Private Cloud\n因為我自己有域名，這裡我就用我自己的域名 (shiun.me) 來示範\nStep 1: [Provider] Endpoint Service 設定 Private DNS name 進入 VPC \u0026gt; 左側欄 Endpoint services:\n選取 Endpoint service 展開 Actions menu 點擊 Modify private DNS name 配置 Private DNS name:\n勾選 Associate a private DNS name with the service 輸入 Private DNS name: 我輸入 demoprivatelink.shiun.me (請依照你自己擁有的域名自由修改這邊的值) Step 2: [Provider] 域名驗證 創建好之後，Domain verification status 會顯示 Pending verification， 這時候我就要根據指示創建一個 TXT Record 證明這個 shiun.me 域名歸你所管，我們可以從 Endpoint service detail 找到 創建 Record 時所需要設定的值，如下圖紅框所示\n所以我打開我的 DNS 服務配置 TXT Record\n注意： 下面截圖的畫面，你不見得會跟我一樣 也許你是用 Route53 也許你是用其他域名註冊商本身自己提供的 DNS 服務\n創建完 TXT Record 之後，可能要稍待幾分鐘 (例如 5 mins)，等待 DNS Propagation。\n我等了大概 5 mins，回到 VPC 頁面 \u0026gt; 左側欄 Endpoint services:\n選取 Endpoint Service 展開 Actions menu 點擊 Verify domain ownership for private DNS name 這時候再等一下下，約一分鐘，重新整理一下頁面，就會看到 Domain verification status 變成 Verified，如果發現還沒 Verified的話，請你：\n再次檢查 DNS Record 是否有配置錯誤 (Type, value… 等) 再次稍等一下下，可以用 dig TXT \u0026lt;domain\u0026gt; 來檢查看看 Answer 是否配置正確 重新 Verify domain ownership for private DNS name Step 3: [Consumer] VPC Interface Endpoint 啟用 Private DNS name 目前身份是 Consumer\n同理，身為 Consumer 這方，VPC Interface Endpoint 這裡也需要啟用 Private DNS name 的支持，可以看到下圖紅框處，Private DNS name enabled 顯示 No，表示目前還尚未啟用此功能\n進入 VPC \u0026gt; 左側欄 Endpoints:\n選取 Endpoint 展開 Actions menu 點擊 Modify private DNS name 在 Modify private DNS name settings section 中，勾選 Enable for this endpoint\nSave 之後，會變成 Pending 狀態\n直到它變成 Available 後，我們再次 SSH 連線到 Private Subnet 的 EC2 Instance\ncurl -4 範例輸出：\n$ curl -4 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; curl -6 範例輸出：\n$ curl -6 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 下圖為 Private Instance 使用 curl 指令分別用 IPv4 和 IPv6 Client 調用 Private DNS name 的執行結果截圖：\n從上面的結果可以看到，我們成功使用 Private DNS name 來存取 Service Provider 的 NLB 囉\n這裡還可以做一個小實驗，我用自己的筆電打開 Terminal (不是 AWS 上 Consumer VPC 裡面的 EC2 instance 唷)，調用 curl 看看:\n$ curl -4 demoprivatelink.shiun.me curl: (6) Could not resolve host: demoprivatelink.shiun.me $ curl -6 demoprivatelink.shiun.me curl: (6) Could not resolve host: demoprivatelink.shiun.me 下圖為我在自己筆電上執行的結果截圖： 我用自己筆電執行的結果是想告訴讀者，PrivateLink 很安全又好用！我們在 AWS 環境外面是真的沒辦法調用到 Endpoint service 唷\n補充 - 透過 PrivateLink 訪問 NLB，那 Service Provider 看得到 Client 原始 IP 地址嗎？ 首先，身為 Consumer 是透過 VPC Interface Endpoint 去訪問到 NLB 的，我已經找到 VPC Interface Endpoint 的 IP 地址如下:\nIPv4: 10.0.133.54 IPv6: 2600:1f14:3231:3002:d93:b5b3:d78c:2e0a 下圖紅框處為 Consumer 那邊的 VPC Interface Endpoint IPv4 和 IPv6 地址： 現在我們就來打流量看看吧\n在 Consumer 這方，我總共輸入了以下指令，也得到相應的 Response:\n[ec2-user@ip-10-0-137-213 ~]$ curl -4 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -6 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -4 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -6 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 回到 Service Provider 那邊，我已經有去找到 NLB ENI，其 IPv6 地址為: 2600:1f14:2549:300:17c1:6c5d:ba29:ce1\n我在 Provider 這邊，透過跳板機 SSH 連線到 NLB 背後的 Target Instance 來觀察 Httpd access logs:\n2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:04:31:12 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:04:31:12 +0000] \u0026#34;GET / HTTP/1.0\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/7.88.1\u0026#34; 2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:04:31:13 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 2600:1f14:2549:300:7935:fbac:a7b4:84d5 - - [08/Jan/2025:04:31:16 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;ELB-HealthChecker/2.0\u0026#34; 2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:04:31:18 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:04:31:24 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 可以注意到來源地址都是 NLB 的 IPv6 address！\n也就是說，Provider 看不到真實來源 IP 地址！\n那怎麼辦？我就是有一些需求需要看到真實來源 IP 地址… 那請繼續往下看，我們會需要 Proxy Protocol 的幫助！！！\n那如果我現在配置了 Proxy protocol 呢？ 關於配置 Httpd Proxy Protocol 支持，這裡先省略，不是本文重點，總之我已經開啟 Proxy Protocol 支持了\n於是我再次回到 Consumer 的 EC2 Instance 對 VPC Interface Endpoint 發出請求\n[ec2-user@ip-10-0-137-213 ~]$ curl -4 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -6 vpce-0d3d9cfb73e4becfd-iiv45fgg.vpce-svc-0ceb593b3ba202634.us-west-2.vpce.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -4 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; [ec2-user@ip-10-0-137-213 ~]$ curl -6 demoprivatelink.shiun.me \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 回到 Service Provider 那邊，觀察 NLB Target instance 中的 Httpd access logs:\n10.0.137.213 - - [08/Jan/2025:04:37:43 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 2600:1f14:3231:3002:5b88:e457:66c1:43ad - - [08/Jan/2025:04:37:46 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 10.0.137.213 - - [08/Jan/2025:04:37:50 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 2600:1f14:3231:3002:5b88:e457:66c1:43ad - - [08/Jan/2025:04:37:52 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.5.0\u0026#34; 下圖為 Provider 那邊的 NLB Target instance 截圖畫面：\n根據上述的各個輸出結果，可以觀察到：\n10.0.137.213 確實是 Consumer Private Instance 的 Private IPv4 address\n2600:1f14:3231:3002:5b88:e457:66c1:43ad 也確實是 Consumer Private Instance 的 IPv6 address\n請見下圖，這是 Consumer 那邊的 Private instance，紅框處就是該 Instance 的 IP 地址\n所以如果有啟用 Proxy Protocol 的話，是可以看到原始來源 IP 的喔！！\nTroubleshooting - [Consumer] 配置 VPC Interface Endpoint 的 SG 如果你沒收到回應，你可以檢查一下 VPC Interface Endpoint 的 SG 是否配置正確，我只有配置一個 Inbound rule 允許 EC2 SG 的流量打進來，至於 Outbound rule 就不需要配置，因為 SG 是 Stateful\nResources 如何將現有 NLB IPv4-only 架構升級為 Dual-stack - Shiun AWS PrivateLink 現在支援跨區域連線功能 - AWS 你知道嗎？此 AZ 非彼 AZ - Shiun Notion Site Manage DNS names for VPC endpoint services - Amazon Virtual Private Cloud AWS PrivateLink Pricing – AWS ","permalink":"https://shiun.me/blog/aws-privatelink-in-depth-tutorial-nlb-dual-stack-and-private-dns-name-integration/","summary":"在前一篇教學中 如何將現有 NLB IPv4-only 架構升級為 Dual-stack，我們已經配置好一個支援 Dual-stack 的 NLB 了。\n現在假設有一個需求：「客戶需要調用我們的 API，且他們也使用 AWS。」有沒有辦法不經過 Internet，而是直接透過 AWS 的骨幹網路 (Backbone)，將流量送到我們的 NLB？同時避免使用 VPC Peering 或 Transit Gateway，確保網路不完全打通，以降低潛在風險，例如資料洩漏或不必要的安全隱患。\n答案就是 PrivateLink\n什麼是 PrivateLink？ AWS PrivateLink 是一種安全的網路技術，允許服務提供者 (Service Provider) 將他們的服務透過 VPC Endpoint 暴露給使用者 (Service Consumer)，而不需要將流量經過公網 (Internet)。它能確保所有流量都在 AWS 的骨幹網路內部傳輸，提供高安全性、低延遲的解決方案。\nPrivateLink 的核心特點:\n避免暴露服務到公網： Service Provider 的服務不需要有 Public IP，Consumer 可以透過 Private IP 訪問這些服務。 簡化網路架構： 無需設定 VPC Peering、Transit Gateway，所以不用擔心整個網路打通的安全風險。 跨帳號支持： Service Provider 和 Consumer 可以位於不同 AWS 帳號，甚至不同 AWS 組織。 多 Region 支持： PrivateLink 現在也支援跨區域的流量傳輸 (需額外配置)。(Reference) 可以自定義的 Private DNS name： Consumer 可以直接使用 Service Provider 提供的 Private DNS Name 調用服務。 Terminology Service Consumer: 使用服務的一方。Consumer 會在自己的 VPC 中建立 VPC Interface Endpoint 來連接到 Provider 的 Endpoint service。Consumer 必須等待 Provider 接受連接請求後，才能開始使用服務。透過這種方式，Consumer 可以安全地存取 Provider 的服務，而無需經過公有網路。 Endpoint: VPC Interface Endpoint (VPCE) 是一個彈性網路介面，具有私有 IP 位址。它作為進入 AWS 服務的進入點，讓 VPC 中的資源可以私密地存取這些服務。在 PrivateLink 架構中，這是 Consumer 端建立的元件。 Service Provider: 提供服務的一方。Provider 需要建立 Endpoint service 並將其與 NLB 或是 GWLB 關聯，然後可以選擇性地允許哪些 AWS 帳戶可以連接到此服務。Provider 負責接受或拒絕來自 Consumer 的連接請求。 Endpoint service: Endpoint service 是由 Service Provider 建立的服務，可以與 Network Load Balancer (NLB) 或是 Gateway Load Balancer (GWLB) 關聯。這個服務允許其他 AWS 帳戶 (Consumer) 通過 VPC Interface Endpoint 連接到 Provider 的服務。 Section 1: 配置 PrivateLink Step 1: [Provider] 創建 Endpoint service 在 Section 1 - Step 1，我們要扮演 Service Provider，我們是提供 Service 的人。","title":"AWS PrivateLink 深入教學：NLB、Dual-stack 與 Private DNS name 整合實作"},{"content":"目標 將現有的 IPv4-only NLB 升級為支援 Dual-stack 讓 Target Group 從純 IPv4 轉換為支援 IPv6 確保整個架構能同時處理 IPv4 和 IPv6 的流量 我們將逐步完成這個轉換過程，包括配置 VPC、更新 NLB 設置、修改 Security Groups，以及設定 Target Groups。每個步驟都會提供詳細的操作說明和技術細節，幫助您順利完成這個升級過程。\n現有架構 現在的架構是一個很基本的 IPv4 NLB(Internet-facing) → Target Group (IPv4 Instance type ) 的架構\n點擊展開以查看詳細內容 VPC NLB\nDetails Security Group Target Group\nInstance Security Group Section 1: NLB IPv4 → Dual-stack Step 1: VPC 新增 IPv6 CIDR 進入 VPC 頁面：\n選取 VPC 展開 Actions menu 點擊 Edit CIDRs 新增 IPv6 CIDR：\n點擊 Add new IPv6 CIDR 選擇 Amazon-provided IPv6 CIDR block Step 2: 為 NLB ENI 所處的 Subnet 分配 IPv6 CIDR 在 AWS Console 常常都會迷路，要配置的東西很多，又散亂在不同地方，所以我習慣到 NLB 的頁面直接找到他所處的 Subnet，如下圖，可以看到 NLB Details 頁面中有一個 Availability Zones 的地方下面的連結就是 Subnet 連結\n成功進入 Subnet Details 頁面後：\n展開 Actions menu 點擊 Edit IPv6 CIDRs 從 VPC CIDR 中，切一個 /64 的 IPv6 CIDR 給這 Subnet\nStep 3: 更新 NLB IP address type 當 NLB 所在的 Subnet 已經分配了 IPv6 CIDR 範圍時，可以為 NLB 的 ENI 分配 IPv6 address。不過，NLB 的 ENI 是由 AWS 自動管理，我們無法直接操作或管理這些 ENI。要為 NLB 的 ENI 分配 IPv6 address，需要修改 NLB 的 IP address type，例如將其設定為 dualstack，AWS 會自動為 ENI 分配對應的 IPv6 位址。\n更改 NLB address type 前，可以去觀察一下 NLB 的 ENI，確實還沒有 IPv6 address (下圖紅框所示)\n回到 NLB Details 頁面：\n展開 Actions menu 點擊 Edit IP address type 把 Load balancer IP address type 改成 Dualstack\n再次回到 ENI 頁面，再次觀察 NLB 的 ENI ，可以注意到，已經從 Subnet CIDR 中 Assign 一個 IPv6 address 給 NLB ENI 了 (下圖紅框處)\nStep 4: 修改 NLB 的 Security Group 允許 IPv6 流量 我們的 NLB 是 Internet-facing 的，原本的 Security Group 只允許任何來源的 IPv4 流量，但我們要接收 IPv6 Client 的流量，勢必就需要修改 Security Group 以允許 IPv6 的流量進入 NLB。\n要找到他的 Security Group：\n可以進入 NLB Detail 的頁面中 \u0026gt; Security 頁籤 就可以找到這個 NLB 的 SG (Security Group) 進入 Security Group 頁面：\n點擊 Edit inbound rules 配置 Inbound rule 允許 IPv6 流量：\n點擊 Add rule Type: HTTP Source: Anywhere-IPv6 Description: Allow all HTTP traffic (IPv6) 現在可以測試看看能不能把請求成功打進去 NLB 而且還得到回應， curl -6 是 curl 工具的一個選項，專門用於指定使用 IPv6 進行請求，如果要用 IPv4 進行請求就是用 -4\n請把 curl -6 後面那串換成你自己的 NLB domain name\n請把 curl -6 後面那串換成你自己的 NLB domain name\n請把 curl -6 後面那串換成你自己的 NLB domain name\ncurl -6 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com 你會發現打不通，沒有回應！！\n你會發現打不通，沒有回應！！\n你會發現打不通，沒有回應！！\nStep 5: 修改 Route table 以允許 IPv6 流量出去 為什麼剛才發出 HTTP 請求後得不到回應呢？原因是：\n上方指令，我強制使用 IPv6，所以我們是 IPv6 Client 因為 NLB 想要把流量打到外面去，但是路由表內並沒有匹配的 Route 允許他說「當目的地是 XXX IPv6 地址時，下一跳要給誰」 所以我們要修改 Route Table，NLB 才有辦法把流量送回來我們這裡 所以！！現在要來修改 NLB ENI 所處的 Subnet 其關聯的 Route table，確保其有 ::/0 相關的規則，讓 IPv6 流量可以出去 VPC\n進入 NLB Detail 頁面中，點擊 Availability Zones 下方的連結，找到 NLB 所在的 Subnet\n進入 VPC Subnets Details 頁面點擊 Route Table\n進入 Route table 頁面：\n勾選 Route Table 展開 Actions menu 點擊 Edit routes 新增 route:\nDestination: ::/0 Target: Internet Gateway 改好後，再次發出 HTTP 請求\n$ curl -6 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com \u0026lt;h1\u0026gt;Hello World from ip-10-0-138-176.us-west-2.compute.internal\u0026lt;/h1\u0026gt; 太棒了！！終於收到 Response 了，到這邊，其實可以算是遷移到 Dual-stack 的目標完成一半了！\n但是我要追求的是 Fully dual-stack! 也就是 Target Group 那邊也是要 Dual-stack\n(Optional) SSH 連線到 Target Group 的 Instance 觀察 Logs 我這邊有用跳板機，連線進去 Target Group 的 Instance 捕捉一下 Private IPv4 Instance 的 Apache httpd Logs，執行以下指令就可以查看 Logs：\nsudo tail -f /var/log/httpd/access_log 一樣我先回到自己電腦，用 curl 命令對 NLB 發出請求：\ncurl -6 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com 然後回到 EC2 Instance 查看 httpd access logs，可以觀察到，來源地址是 NLB 的 Private IPv4 address (10.0.9.20):\n10.0.9.20 - - [07/Jan/2025:20:49:00 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;ELB-HealthChecker/2.0\u0026#34; 10.0.9.20 - - [07/Jan/2025:20:49:02 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.7.1\u0026#34; 透過這個觀察，我們可以知道，當我身為 IPv6 Client，向 NLB 發出 HTTP 請求後，當流量到達時 NLB ，NLB 會利用自身的 Private IPv4 address 去和 Target 通信，這也是為什麼我們在 EC2 Instance 觀察 httpd access log 時，看到的來源 IP 地址並不是我電腦的 IPv6 address，而是 NLB 的 Private IPv4 address。\n當然我們也可以觀察用 IPv4 去調用，會怎樣：\ncurl -4 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com 一樣回到 EC2 Instance 查看 httpd access log，觀察到這裡可以直接看見我電腦的 IPv4 address:\n61.64.29.207 - - [08/Jan/2025:01:33:29 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.7.1\u0026#34; 會這樣是因為流量從頭到尾都是 IPv4，NLB 不需要在中間幫我們做任何協議的轉換：\nIPv4 Client -\u0026gt; Dual-stack NLB -\u0026gt; IPv4 Target Group Instance (Optional) 利用 VPC Flow logs 觀察 NLB 的回應 VPC Flow Logs 的建置不是本文重點，關於怎麼建置在煩請查閱 AWS 官方文檔\n我建立了 VPC Flow logs 來觀察 NLB 的 ENI，於是觀察到以下東西：\nNLB 確實是以自身 IPv6 address 作為來源地址，將後端應用 (Target Group) 的 Response 回傳到我的電腦\n為什麼 VPC Flow logs 上面沒見到任何我的筆電 IPv6 地址作為 src？只有看到我的筆電 IPv6 被作為 dst?? 可以看下圖\n其實即便我今天是 IPv4 Client 也是一樣，就是看不到我的 IP 地址被作為 src 記錄在 VPC Flow logs 這個問題真的讓我很匪夷所思，想了很久，只能**猜測：**某些流量在 NLB 的內部處理過程中未經過 ENI，或者經過特殊的封包處理路徑，這些流量可能不會被 Flow Logs 捕捉到。 (純屬猜測，如果有人知道正確答案希望能分享讓我知道，真的很想探究背後的原因) NLB IPv6 address: 2600:1f14:2549:300:17c1:6c5d:ba29:ce1\nMy IPv6 address: 2407:4b00:1c02:77d9:8041:f560:157a:c42c\nSection2: 讓 Target Group 也變成 Dual-stack Step 1: 分配 IPv6 CIDR 給 Target Group 的 Instance 所處 subnet 要找到 EC2 所處的 Subnet，我習慣進入 EC2 頁面，直接找到這台 EC2 所處的 Subnet\n進入 VPC Subnet 頁面：\n選取 Subnet 展開 Actions menu 點擊 Edit IPv6 CIDR 從 VPC 中分配一個 /64 的 IPv6 CIDR 子網給他\n注意： 配給這個子網的 CIDR 不要和同 VPC 中的其他 Subnet CIDRs 有 Overlapping!\nStep2: Assign IPv6 address 給 Instance 進入 EC2 頁面：\n選取 Instance 展開 Actions Menu 點擊 Networking 點擊 Manage IP addresses Assign IPv6 Address:\n展開 eth0 在 IPv6 addresses 的 Section 下，點擊 Assign new IP address，欄位可以不填寫，他會從子網的 IPv6 CIDR 中 Auto-assign Step 3: 建立 IPv6 Instance type target group 進入 EC2 \u0026gt; Target Group 頁面：\n點擊 Create Target Group 依照下方配置 Target Group\nTarget type: Instances Target group name: ipv6-private-instance-tg Protocol: Port TCP 80 IP address type: IPv6 VPC: Instance 所處的 VPC 其他保持預設，然後按下 Next\nRegister targets:\n點擊 Unassigned Manage IP addresses 成功進到 Manage IP addresses 頁面：\n展開 eth0 勾選 Enable Assign primary IPv6 IP 點擊 Save 回到剛才 Target Group 的頁面：\n點擊右上角 Refresh icon (如下圖紅框所示) 成功的話，你會看到表格中 Primary IPv6 address 欄位從原本的 Unassigned 變成顯示對應的 IPv6 address Register targets:\n勾選 instance 點擊 Include as pending below 點擊 Create target group Step 4: 修改 NLB Listener 的 Target 在 NLB 中，一個 Listener（例如 TCP 80 Port Listener）背後只能關聯一個 Target Group，無法同時直接關聯多個 Target Group。\n要跟上趨勢當然就要用 IPv6，所以就來把原本的 IPv4 Target 改成剛才創建好的 IPv6 Target\n進入 NLB Detail 頁面：\n勾選 Listener 點擊 Edit listener 修改成剛才所創建的 IPv6 Target Group\n切換 TG 後，需要等一下下，因為 NLB 還要做 Health Check 需要一點時間\n這時候我回到先前所 SSH 連線的 EC2 Instance 持續觀察 httpd access log 也會發現 Health check 的流量，從原本的 NLB Private IPv4 address 變成 NLB IPv6 address\n補充： 這是目前 NLB ENI 被 Assign 的 Addresses NLB Private IPv4 address: 10.0.9.20 NLB IPv6 address: 2600:1f14:2549:300:7935:fbac:a7b4:84d5\n到這裡，我們已經成功把 Target Group 升級成 Dual-stack 了！\nStep 5: 測試 IPv6 流量 現在我們來使用 curl -6 指令向 Dual-stack NLB 發出 HTTP 請求：\ncurl -6 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com 流量路徑會是：\nIPv6 Client -\u0026gt; Dual-stack NLB (Layer 4) -\u0026gt; IPv6 Target Group Instance NLB 運作於第 4 層（傳輸層），專門處理傳輸層協議（如 TCP 和 UDP）的流量。由於 NLB 不會對應用層數據進行處理，因此它可以保留封包的原始來源 IP 地址，使後端 Target Group（例如 EC2 Instances）能直接看到 Client 的 IP 地址。\n但是要注意，不同的 Target Group 配置會讓保留原始 Client IP 行為略有不同：\nTarget Group 使用實例 ID（Instance ID）： NLB 會直接保留原始的 Client IP 地址，無需額外設定。 Target Group 使用 IP 地址（IP Address）： 如果使用的是 TCP 或 TLS 協議，NLB 預設不會保留 Client IP 地址。在這種情況下，可以啟用 Proxy Protocol v2，以將原始的 Client IP 地址嵌入到傳遞給目標的封包中。 以下是 Target Instance 上 httpd 的 access log，可以看到記錄的是客戶端的 IPv6 地址 2407:4b00:1c02:77d9:8041:f560:157a:c42c (這就是我電腦的 IPv6 address):\n2407:4b00:1c02:77d9:8041:f560:157a:c42c - - [07/Jan/2025:22:01:05 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.7.1\u0026#34; 也來試試看 IPv4 Client 發出 HTTP 請求：\ncurl -4 http://nlb-from-ipv4-to-dual-stack-a7d193e605191856.elb.us-west-2.amazonaws.com 你會發現後端看到的來源 IP 是 NLB 的 IPv6 address\n2600:1f14:2549:300:17c1:6c5d:ba29:ce1 - - [08/Jan/2025:02:08:26 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 69 \u0026#34;-\u0026#34; \u0026#34;curl/8.7.1\u0026#34; (Optional) 既然 NLB 會幫我做協議轉換，那我還是想看到原始客戶端 IP 地址怎麼辦？ 這時候就需要 Proxy Protocol 的幫助了！\n由於這部分不是本文重點，要怎麼在配置 Proxy Protocol 來讓你的 Server 支援 Proxy Protocol 可以參考我的 Notion 筆記，但我這個筆記中是以 Nginx 為示範，跟本文使用 Httpd 不一樣唷：\n連結：配置 Nginx 支持 Proxy Protocol v2 若之後有時間我會重新整理 Notion 筆記中的 Proxy Protocol 的配置，再上傳到部落格\nResources VPC CIDR blocks - Amazon Virtual Private Cloud Logging IP traffic using VPC Flow Logs - Amazon Virtual Private Cloud 配置 Nginx 支持 Proxy Protocol v2 - Shiun Notion 如何將現有 NLB IPv4-only 架構升級為 Dual-stack - Shiun Notion Dual-stack network design in AWS - Shiun Notion Dual-stack IPv6 architectures for AWS and hybrid networks | Amazon Web Services ","permalink":"https://shiun.me/blog/upgrading-existing-nlb-ipv4-only-to-dual-stack/","summary":"目標 將現有的 IPv4-only NLB 升級為支援 Dual-stack 讓 Target Group 從純 IPv4 轉換為支援 IPv6 確保整個架構能同時處理 IPv4 和 IPv6 的流量 我們將逐步完成這個轉換過程，包括配置 VPC、更新 NLB 設置、修改 Security Groups，以及設定 Target Groups。每個步驟都會提供詳細的操作說明和技術細節，幫助您順利完成這個升級過程。\n現有架構 現在的架構是一個很基本的 IPv4 NLB(Internet-facing) → Target Group (IPv4 Instance type ) 的架構\n點擊展開以查看詳細內容 VPC NLB\nDetails Security Group Target Group\nInstance Security Group Section 1: NLB IPv4 → Dual-stack Step 1: VPC 新增 IPv6 CIDR 進入 VPC 頁面：\n選取 VPC 展開 Actions menu 點擊 Edit CIDRs 新增 IPv6 CIDR：","title":"如何將現有 NLB IPv4-only 架構升級為 Dual-stack"},{"content":"前言 - 現有架構的挑戰 最近，我在一個 Serverless 專案中遇到了一個讓人頭疼的挑戰。這個專案運行在多個 Lambda Functions 之上，為了避免冷啟動帶來的延遲問題影響使用者體驗，需要確保 Functions 處於「熱啟動」狀態（Warm Start），如何有效管理大量的預熱 (prewarm) 操作變得非常棘手。\n常見的解決方案之一，是為每個 Lambda Function 配置一個 EventBridge Scheduler，雖然可以達到預熱效果，但：\n管理成本高：需要單獨為每個 Lambda Function 配置 Scheduler，數量多難以管理。 靈活性不足：難以快速響應需求變更，重新部署新的 Lambda Function 時，需要修改 Scheduler 的 Target。 基於這些挑戰，我開發了 Tag-Based Lambda Warmer Terraform Module，提供了一種低成本、高靈活性的解決方案。\nTag-Based Lambda Warmer 介紹 Tag-Based Lambda Warmer 的核心理念非常簡單：\n將需要被預熱 (prewarm) 的 Lambda Function 加上指定的 Tag（例如：Prewarm=true）。\n當你部署 Terraform Module 後，EventBridge Schduler 會定期調用 Tag-Based Lambda Warmer，Warmer 會根據 Tag 自動篩選需要被預熱的 Lambda Functions\n這套解決方案特別適合以下情境：\n低流量、間歇性工作負載：例如開發環境或小型應用中的 Lambda Functions，需要偶爾處於熱啟動狀態，但不需要持續高效能。 多環境管理：通過不同的 Tag（例如 Environment=dev 或 Environment=prod），可以輕鬆在多個環境中靈活部署。 成本敏感型專案：對於中小型 Serverless 應用，這種解決方案能有效降低運維成本。 還是要提醒，這種透過定期調用來保持熱啟動狀態的解決方案，只能讓你保持至少有一個熱啟動狀態的 Execution Environment，今天流量突然有一個高峰，碰上冷啟動是不可避免的\n我已經將 Tag-Based Lambda Warmer 寫成 Terraform Module 並發布到 Terraform Registry 囉，你只需要在你的 Terraform 配置中引用模組:\nmodule \u0026#34;lambda_warmer\u0026#34; { source = \u0026#34;aws-educate-tw/tag-based-lambda-warmer/aws\u0026#34; # version = \u0026#34;0.2.1\u0026#34; aws_region = \u0026#34;ap-northeast-1\u0026#34; # Optional: Custom configuration environment = \u0026#34;prod\u0026#34; prewarm_tag_key = \u0026#34;Project\u0026#34; prewarm_tag_value = \u0026#34;MyProject\u0026#34; lambda_schedule_expression = \u0026#34;rate(5 minutes)\u0026#34; scheduler_max_retry_attempts = 0 invocation_type = \u0026#34;Event\u0026#34; } 執行流程 EventBridge Scheduler 定時觸發 Tag-Based Lambda Warmer Function。 Warmer Function 調用 Lambda API，獲取所有 Lambda Functions 列表。 根據指定的 Tag Key 和 Value，篩選出需要預熱的 Lambda Functions。 非同步觸發這些函數進行預熱。 執行日誌記錄在 CloudWatch Logs，方便後續監控與調試。 優點 如果某個 Lambda Function 不再需要預熱，只需 移除 Tag 或修改其值；如果日後又有預熱需求，只需重新加上 Tag，即可輕鬆加入預熱名單。\n這種 Tag-Based 的設計具有以下幾個關鍵優勢：\n靈活性高： 可以快速調整預熱範圍，無需重新部署基礎設施。 支援客製化篩選條件，例如基於專案名稱 (Project=MyApp) 或環境 (Environment=prod) 的 Tag。 低成本： 只需一個 EventBridge Scheduler 和一個 Lambda Function 的組合，即可實現多個 Lambda 的集中化管理。 避免了高昂的 Provisioned Concurrency 成本，完全按需付費。 簡單易用： 部署 Module 後，唯一需要做的就是為 Lambda Functions 添加或移除 Tag，無需其他額外配置。 (視情況需要新增一小段程式碼) 注意事項 那些需要被預熱的 Lambda Functions 視情況可能需要在請求一到達時先檢查是否是 Prewarming 操作，避免被預熱後就執行了業務邏輯\n以下是 Python 範例程式碼，你可以將以下程式碼插入至 lambda_handler 函數中，用來檢查是否是 Prewarming 操作：\ndef lambda_handler(event, context): # Identify if the incoming event is a prewarm request if event.get(\u0026#34;action\u0026#34;) == \u0026#34;PREWARM\u0026#34;: logger.info(\u0026#34;Received a prewarm request. Skipping business logic.\u0026#34;) return { \u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;Successfully warmed up\u0026#34; } 結語 Tag-Based Lambda Warmer 是一個低成本、高靈活性的 Lambda 預熱解決方案。無論是小型專案還是多環境部署，它都能幫助你有效管理和優化 Lambda Functions 的冷啟動問題。\n如果你對這個模組感興趣，歡迎參考以下資源：\nGitHub Repository: terraform-aws-tag-based-lambda-warmer Terraform Registry: AWS Lambda Warmer Module 有任何問題，歡迎提交 Issue。希望這個工具能幫助你更好地管理 Serverless 專案中的 Lambda Functions！\n","permalink":"https://shiun.me/blog/tag-based-lambda-warmer-low-cost-high-flexibility/","summary":"前言 - 現有架構的挑戰 最近，我在一個 Serverless 專案中遇到了一個讓人頭疼的挑戰。這個專案運行在多個 Lambda Functions 之上，為了避免冷啟動帶來的延遲問題影響使用者體驗，需要確保 Functions 處於「熱啟動」狀態（Warm Start），如何有效管理大量的預熱 (prewarm) 操作變得非常棘手。\n常見的解決方案之一，是為每個 Lambda Function 配置一個 EventBridge Scheduler，雖然可以達到預熱效果，但：\n管理成本高：需要單獨為每個 Lambda Function 配置 Scheduler，數量多難以管理。 靈活性不足：難以快速響應需求變更，重新部署新的 Lambda Function 時，需要修改 Scheduler 的 Target。 基於這些挑戰，我開發了 Tag-Based Lambda Warmer Terraform Module，提供了一種低成本、高靈活性的解決方案。\nTag-Based Lambda Warmer 介紹 Tag-Based Lambda Warmer 的核心理念非常簡單：\n將需要被預熱 (prewarm) 的 Lambda Function 加上指定的 Tag（例如：Prewarm=true）。\n當你部署 Terraform Module 後，EventBridge Schduler 會定期調用 Tag-Based Lambda Warmer，Warmer 會根據 Tag 自動篩選需要被預熱的 Lambda Functions\n這套解決方案特別適合以下情境：\n低流量、間歇性工作負載：例如開發環境或小型應用中的 Lambda Functions，需要偶爾處於熱啟動狀態，但不需要持續高效能。 多環境管理：通過不同的 Tag（例如 Environment=dev 或 Environment=prod），可以輕鬆在多個環境中靈活部署。 成本敏感型專案：對於中小型 Serverless 應用，這種解決方案能有效降低運維成本。 還是要提醒，這種透過定期調用來保持熱啟動狀態的解決方案，只能讓你保持至少有一個熱啟動狀態的 Execution Environment，今天流量突然有一個高峰，碰上冷啟動是不可避免的","title":"[Terraform Module] 一個低成本、高靈活性，輕鬆預熱上百個 Lambda Function 的解決方案：Tag-Based Lambda Warmer"},{"content":"Overview 就在 2024/11/20 AWS 釋出了重磅消息 — 「Amazon Aurora Serverless v2 supports scaling to zero capacity」，簡單來說這個功能能夠讓 Aurora 在閒置一段時間後，自動停止實例 (Auto-pause)，停止期間不會收取「執行實例小時數費用」 (Aurora Capacity Units = 0)，達到所謂的 Truly Serverless!! 官方把這個新功能稱為 Auto-pause ，在這個功能釋出以前， Aurora Capacity Units (ACU) 最低最低只能設定成 0.5。\n實際 RDS 定價 其實還有其他收費，像是儲存成本、 IOPS、傳輸… 等等，詳細內容敬請參考官方文件 (連結)\nAmazon Aurora vs RDS for MySQL 在 us-west-2 的定價比較:\nAurora Serverless v2 每個 ACU 一小時 0.12 美金 RDS for MySQL - db.t4g.micro 一小時 0.016 美金 約等於一 ACU 可以開 7.5 小時的 db.t4g.micro，建議大家要根據自己的實際情境去好好計算成本喔，並不是 Serverless = 經濟實惠。\n一、Auto-pause 介紹 自動暫停 什麼是自動暫停？ 當資料庫一段時間內沒有任何連線時，Aurora Serverless v2 會自動將其暫停，並釋放所有計算資源，將容量縮減到 0 ACUs。 在暫停期間，您只需要支付儲存費用，計算資源費用暫停。 暫停條件 沒有任何連線（用戶活動）在指定的閒置時間內觸發。 支援的閒置時間範圍：5 分鐘 (300 秒) 至 24 小時 (86,400 秒)。 自動恢復 什麼是自動恢復？ 當資料庫收到第一個連線請求時，系統會自動恢復，並動態分配計算資源。 恢復時間約為 15 秒。 唯獨一定要特別注意！如果你的 Aurora Instance 超過 24 小時以上是停止狀態，那冷啟動會超過 30 秒以上\n官方文件描述如下:\nIf an Aurora Serverless v2 instance remains paused more than 24 hours, Aurora can put the instance into a deeper sleep that takes longer to resume. In that case, the resume time can be 30 seconds or longer\nReference: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html#auto-pause-whynot\n二、建立 Aurora Serverless v2 注意 Engine Version！以下版本才支援 ACU 設定成 0\nIf you\u0026rsquo;re using Aurora PostgreSQL, the database engine must be running at least version 16.3, 15.7, 14.12, or 13.15. If you\u0026rsquo;re using Aurora MySQL, the database engine must be running version 3.08.0 or higher. 配置 Engine\n選擇 Aurora (MySQL Compatible) Engine version: 3.08.0 以上 配置資料庫 Credential\n選 Dev/Test 設定密碼並再次輸入密碼 配置 Cluster / Instance\n選擇 Aurora Standard 選擇 Serverless v2 Minimum capacity: 0 配置 Connectivity\nPublic access 打開 (生產環境不建議打開)\nVPC security group\nCreate new New VPC security group name: aurora-serverless-v2-sg 不建議生產環境這樣用，由於這部分並非本文主要教學目標，基於簡化複雜配置，才會將 RDS 的 Public access 打開，並設定允許所有來源 IP。 在生產環境中，建議要切好網段，配置好 RDS Subnet Group，將 RDS 放置於 Private Subnet，並且基於最小需求設定 NACL, Security Group。Lambda 也需要部署在 VPC 而需要連到 Internet 需要配置 NAT Gateway。\n其餘保持預設 \u0026gt; Create database 三、如何觀察是否成功 Auto-pause? 注意！！！即使執行個體進入自動暫停，RDS Console 仍會顯示為 Available，這是 Aurora Serverless 的特性。\n1. 查看 RDS 主控台中的執行個體狀態 步驟： 進入 AWS Management Console。 選擇 Amazon RDS \u0026gt; Databases。 在資料庫清單中找到您的 Aurora Serverless V2 集群或執行個體。 檢查執行個體的 狀態（Status）。 如果顯示為 Available 且未顯示負載數據，可能已自動暫停。 注意：在暫停期間，狀態仍會顯示 Available，但其行為與正常運行有所不同。 2. 查看 CloudWatch 指標 AWS CloudWatch 提供了多種監控指標，可以確認執行個體是否處於自動暫停狀態。\n關鍵指標:\nACUUtilization 值為 0 時，表示執行個體已縮容到 0 ACUs，即已自動暫停。 ServerlessDatabaseCapacity 值為 0 時，表示目前沒有計算資源分配，也意味著已經自動暫停。 CPUUtilization 值為 0% 時，執行個體沒有進行任何處理，可能處於暫停狀態。 下圖為成功 Auto-pause 的範例，注意 ACUUtilization Metric 有某段時間是成功完全貼平在最底 (0 percent)\n3. 查看事件記錄 AWS 會記錄執行個體的暫停與恢復相關事件。可以到 Cluster \u0026gt; Logs \u0026amp; events 頁籤查看 Recent events\n四、Troubleshooting - 明明沒有活動為什麼 Aurora Serverless v2 沒有 Auto-pause 1. 自動暫停的限制與條件 哪些情況下不會自動暫停？ 有用戶連線活動時 當存在未關閉的用戶連線，系統不會進入暫停狀態。 啟用了某些功能或集成： 邏輯複製 (PostgreSQL) 或 Binlog 複製 (MySQL) 這些功能需要保持活躍，因此不支援自動暫停。 RDS Proxy (很重要！！！如果有使用 RDS Proxy 會導致 Auto-pause 無法啟用喔) RDS Proxy 保持與資料庫的持續連線，導致無法暫停。 跨區集群 (Aurora Global Database) 主要集群的寫入節點無法自動暫停。 次要集群的節點可能會根據優先級部分支持暫停。 2. Failover Priority 與自動暫停的關係 Failover Priority 的行為 優先級 0 和 1 的節點通常不會自動暫停： 這些節點被視為關鍵節點，通常與寫入節點（Writer Instance）行為一致。\n當寫入節點恢復時，這些節點也會自動恢復。\n如果叢集只有單一實例(寫入器)，Failover priority 不會影響 auto-pause 行為。單一實例的自動暫停僅取決於：\n最小容量設為 0 ACU 無使用者連線 達到設定的閒置時間 無使用不相容功能(如複製、Proxy 等) 如何配置 Failover Priority？ 可以設置不同節點的 Failover Priority： 高優先級節點（如 priority = 0 或 1）：保持可用，不進入暫停。 低優先級節點（如 priority = 2 或更高）：根據負載自動暫停。 3. 觀察 instance.log 來排查原因 Aurora writes a separate log file for Aurora Serverless v2 DB instances with auto-pause enabled. Aurora writes to the log for each 10-minute interval that the instance isn\u0026rsquo;t paused. Aurora retains up to seven of these logs, rotated daily. The current log file is named instance.log, and older logs are named using the pattern instance.*YYYY-MM-DD*.*N*.log.\nThe instance.log provides more granular detail about the reasons why an Aurora Serverless v2 instance might or might not be able to pause.\n日誌中常見訊息與排查方法:\n[INFO] No auto-pause blockers registered since time 意義：在設定的自動暫停時間內，沒有阻止暫停的活動。 多執行個體集群的差異： 當讀取節點的活動在暫停時間結束前結束，寫入節點仍可按預期時間暫停。 [INFO] Unable to pause database due to a new database activity 意義：執行個體嘗試暫停，但新連線請求在暫停完成前抵達，阻止了暫停。 [INFO] Auto-pause blockers registered since time: list_of_conditions 意義：列出阻止暫停的所有條件。 解決建議：檢查列出的條件，調整配置或使用方式。 以我這邊為例，進入 RDS Console \u0026gt; 選擇 database 實例 \u0026gt; 切換至 Logs \u0026amp; events 頁籤 \u0026gt; Logs 選取 instance/instance.log \u0026gt; View Logs\n2024-11-28T01:33:12,982 [INFO] Auto-pause blockers registered since 2024-11-27T21:18:00.611Z: database activity before auto-pause timeout, continuous backup lag 2024-11-28T01:43:15,085 [INFO] Auto-pause blockers registered since 2024-11-28T01:33:12.981Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T01:53:15,591 [INFO] Auto-pause blockers registered since 2024-11-28T01:43:15.085Z: database activity before auto-pause timeout, continuous backup lag 2024-11-28T02:03:16,185 [INFO] Auto-pause blockers registered since 2024-11-28T01:53:15.590Z: database activity before auto-pause timeout, continuous backup lag 2024-11-28T02:13:16,790 [INFO] Auto-pause blockers registered since 2024-11-28T02:03:16.185Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T02:23:17,389 [INFO] Auto-pause blockers registered since 2024-11-28T02:13:16.790Z: database activity before auto-pause timeout, continuous backup lag 2024-11-28T02:33:17,989 [INFO] Auto-pause blockers registered since 2024-11-28T02:23:17.389Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T02:43:18,586 [INFO] Auto-pause blockers registered since 2024-11-28T02:33:17.988Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T02:53:19,186 [INFO] Auto-pause blockers registered since 2024-11-28T02:43:18.586Z: database activity before auto-pause timeout, continuous backup lag 2024-11-28T03:03:19,787 [INFO] Auto-pause blockers registered since 2024-11-28T02:53:19.186Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T03:13:20,386 [INFO] Auto-pause blockers registered since 2024-11-28T03:03:19.787Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action 2024-11-28T03:23:20,987 [INFO] Auto-pause blockers registered since 2024-11-28T03:13:20.386Z: database activity before auto-pause timeout, continuous backup lag, service or customer maintenance action ----------------------- END OF LOG ---------------------- 於是我連線進去 Instance 執行以下指令:\n可以 Enable RDS Data API 後，去使用 Query Editor 連線到資料庫唷！\n進入 Query Editor 後 \u0026gt; 可以參考下圖配置連線到 database\nSHOW FULL PROCESSLIST KILL connection_id; -- connection_id 從 PROCESSLIST 中獲得 我的看法和心得 Aurora Serverless v2 的 Auto-pause 功能對我而言非常具有吸引力，特別是因為我有幾個專案都是 Fully Serverless 的架構，而那些專案都有這些特性：「間歇性負載」且「流量較低」，因此 Pay-as-you-go 的計費模式成為我考量的重點。\n在 Aurora Auto-pause 功能推出之前，在 AWS 上要符合 Pay-as-you-go 特性的資料庫選擇只有 NoSQL - DynamoDB。\n我這篇文章對 Pay-as-you-go 的主觀認定，有一個重要指標是「閒置期間不會產生費用」\n由於 DynamoDB 的特性，在實作分頁功能（Pagination）時，若要實現順暢的上一頁和下一頁切換，通常會較為繁瑣，甚至需要額外的開發成本。此外，某些需求在使用關聯式資料庫時會更加直覺，尤其當資料之間存在強烈關聯性且需要高一致性支援的情境下，關聯式資料庫的優勢則更為明顯。\nAurora Serverless v2 的 Auto-pause 滿足了 Serverless 架構對彈性與低成本的需求，同時還能提供關聯式資料庫的能力。但以現況需要冷啟動超過 15 秒，甚至有時候超過 24 hrs 以上沒 Resume 資料庫會發生超過 30 秒的冷啟動，除非使用者可以忍受很高的延遲，不然我認為現況還不足以放到 Production 環境來服務我們專案的使用者。\n也許是可以用一些額外機制讓資料庫提前 Resume (例如某事件發生就去觸發資料庫連線，提前讓資料庫 Resume)，但是我認為這反而引來了額外維護的成本，目前還不夠足以吸引我去投資那樣的精力維護那個機制。\n我相信 AWS 日後一定會把 Aurora 的冷啟動時間優化，我推測 2025 re:Invent 應該可以看到這邊還會有新的重大突破！\n相關連結 Shiun - 如何使用 AWS EventBridge Scheduler 及 Lambda 自動排程調整 AWS Aurora Serverless V2 ACU\nScaling to Zero ACUs with automatic pause and resume for Aurora Serverless v2 - Amazon Aurora\nManaging Aurora Serverless v2 DB clusters - Amazon Aurora\n","permalink":"https://shiun.me/blog/unboxing-amazon-aurora-serverless-v2-auto-pause-acu-0/","summary":"Overview 就在 2024/11/20 AWS 釋出了重磅消息 — 「Amazon Aurora Serverless v2 supports scaling to zero capacity」，簡單來說這個功能能夠讓 Aurora 在閒置一段時間後，自動停止實例 (Auto-pause)，停止期間不會收取「執行實例小時數費用」 (Aurora Capacity Units = 0)，達到所謂的 Truly Serverless!! 官方把這個新功能稱為 Auto-pause ，在這個功能釋出以前， Aurora Capacity Units (ACU) 最低最低只能設定成 0.5。\n實際 RDS 定價 其實還有其他收費，像是儲存成本、 IOPS、傳輸… 等等，詳細內容敬請參考官方文件 (連結)\nAmazon Aurora vs RDS for MySQL 在 us-west-2 的定價比較:\nAurora Serverless v2 每個 ACU 一小時 0.12 美金 RDS for MySQL - db.t4g.micro 一小時 0.016 美金 約等於一 ACU 可以開 7.5 小時的 db.","title":"開箱 Amazon Aurora Serverless v2 Auto-pause Feature (ACU 0)"},{"content":"(原本沒打算寫那麼多，結果每個故事的前因後果，一個連結一個，就越寫越多了)\n最近我從第六屆 AWS Educate 雲端大使計劃畢業了！第六屆大使計劃任職期間真的非常充實且充滿挑戰，我也在這段旅程中成長了非常非常多。\n這篇文章會從我踏上資訊科技之路開始說起，一直到我加入 AWS Educate 雲端大使計劃，再一一回顧我在第六屆 AWS Educate 雲端大使中的一些成就和重要活動，最後也會分享一些我在這段旅程中的收穫和成長，以及我的近期未來規劃。\n我和 AWS 的淵源，我又是怎麼一路成為 AWS Educate 雲端大使 「我覺得我很幸運，我算是在很早期就對於職涯目標很明確，而且到越後期越明確。」\n講到這個，回憶起高中時，學校為了我們選組還有選大學科系有做了兩次興趣測驗，我只知道測驗結果顯示我的興趣鑑別度很明確，而且我在資訊管理學系超級高分，重測一次的結果也是一樣，也因此我很早的志願就是「資訊管理學系」，所以學測完申請學校時，每個學校都填資訊管理學系。後來就進入了輔仁大學讀資管系，現在回頭看，真的沒選錯路，完完全全選對路。\n2022 踏入職場後與 AWS 的第一次接觸 我是從大二開始進入職場實習的，這間公司叫做 eGroupAI，主要產品有兩個，分別是人臉辨識系統和 SaaS 企業管理平台，當時我就是以一個菜鳥實習生身份進入，也是第一次見證到真正業界的開發流程，當時的公司主要使用 Spring Boot，如果要加入公司的正式開發的行列，需要歷經一個實習生考試，沒有考過就是直接掰掰，所以當時白天實習，晚上回到家要瘋狂讀課程和寫作業，還好最後考過了。\n加入正式開發的行列後，第一件事情就是要配置好開發環境，光是裝 IDE, JDK, 配置一堆有的沒的就可以用掉一整個工作天，我當時跟 AWS 的第一次接觸，就是主管叫我去安裝 AWS CLI，配給我一個 IAM User 的 Access Key ID 和 Secret Access Key (簡稱 AKSK)，然後輸入一些指令，當時我其實也不知道那是什麼，問了主管這個是什麼東西，他說因為開發會有一段會經過雲，後端有用到 DynamoDB，這樣你在本地開發時才有權限去處理那邊的東西。\n而也隨著我在學習的過程，我開始對整個軟體的運作流程以及架構逐漸明朗，也因此我與雲端的淵源越來越深，認識的也越來越多。2022 年的某天，我參加了 AWS Educate 舉辦的工作坊，而且是辦在 AWS 辦公室裡面，正好可以去參觀一下。當時在工作坊尾聲時，有一個最大獎，只要回答出今天講的服務的一個實際應用案例，就可以拿走，正好我當時開發時就有處理過 S3 相關的東西，所以我腦海馬上蹦出我在開發上的實踐，最後這個大獎就被我拿走了，當時這個小小成就感影響我蠻大的，讓我有繼續探索雲端技術的動力。\n不夠自信讓我差點卻步申請 AWS Educate 雲端大使計劃 隨後就是我迷上了雲端技術，當時 AWS 大大小小的活動我都一定會參加，但是 AWS 的活動都舉辦在平日，為了參加活動我都必須翹課，而且排班時都要避開 AWS 活動日，而當時我對容器化很感興趣，所以每個活動的下午分場議程我都專挑一些 Modernization 或是 Container 相關的主題，那時候有一個 Solutions Architect (SA) 他叫做 Kerrigan ，我特別喜歡他的演講風格，而且他演講的主題多半都和我感興趣技術有關，那時候我剛開始經營 LinkedIn，當時我認為建立關係是一個很謹慎且重大的事，很怕一個菜雞學生跟大神建立關係會被拒絕，某次演講結束後我就寫了一段訊息然後鼓起勇氣去建立關係，這是我第一次跟原本不認識的人建立關係，而沒多久後我看到被接受邀請的通知就超開心，而且 Kerrigan 也有給予我一些正面回應。現在回頭看，雖然好像只是簡單的建立關係，但對當時的我來說是相當大的鼓舞，這是能讓我繼續往雲端技術探索動力來源之一。\n在 2023 夏天，我看到 5th AWS Educate 雲端大使的招募消息，我當時看到其實就一直很猶豫不決該不該嘗試去報名看看，想報名其實最主要是出自於對雲端技術的熱情，其次是我參加過無數的 AWS 各個大大小小的活動在底下看著台上的雲端大神，也會很想跟他們交流，最後是我自己本來就喜歡面對人群、與人交流及分享知識，所以也渴望自己能在台上展現自己專業的那種感覺；不敢踏出去是因為我對於當時的自己背景很沒有信心，畢竟學歷只是私立學校，英文口說能力又很差對於進入外商的環境來說真的是一個坎 (後來發現不會有英文面試)，最終我一直想辦法要去克服這個坎，我就臨時起意，趕緊去考一張 SAA 證照希望能「武裝」我的履歷，很幸運的是因為我在學校有選修 AWS 的課、工作上有在用 AWS、畢業專題也用 AWS、自己閒暇時間也都會主動探索雲端技術，所以我準備 SAA 時感覺就很像在複習和加強一些細節，所以在 7 天就成功考到了，最後也是成功錄取書審，進入面試關卡。\n面試當天，我進到團體面試就嚇到了，當大家在自我介紹時，發現好多都是來自台大的學生… 我其實當下真的很緊張，大家資歷都很強，有一種「看見世界如此之大」的感覺。我覺得我當時在團體面試時真的太緊張，沒有發揮的很好，而且當時其他人的溝通能力和跨團隊的協調能力非常強，也因此在當時個人面試對我來說至關重要，我必須在個人面試扳回一城，而還好為了個人面試，我面試之前找了身邊非技術背景或是剛開始學習 AWS 的人練習過簡報，以確保我的簡報是足以讓「雲端小白」聽得懂的，在結束後，我一直很擔心不會錄取，畢竟當時我真的很菜，第一次碰到這場面是真的被震撼到，等待通知的過程中總是一直去回想當時有幾題面試官的提出的問題可以回答得更好。\n終於，公告錄取通知的那天到來，我順利錄取了，真的是爽翻天，當時我真的是「抱著滿腔對雲端技術的熱情」加入到 AWS Educate 雲端大使計劃。\n加入 5th 大使計劃後，迎來我的第一個挑戰是 AWS Summit，我需要在攤位上為他人介紹 AWS Educate 平台，不過這種會需要跟人直接面對面的場合真的是我的拿手菜，我發現自己可以在專業上很輕鬆地與他人侃侃而談，也很享受幫他人解決專業問題帶來的成就感，那時是我第一次需要在這種商業場合與他人面對面互動，而我在那刻也明確領悟到我理想的職涯意向就是: 「雲端運算」、「與人互動」、「幫助他人解決問題」\nAWS Educate 雲端大使到底在做什麼？ AWS Educate 雲端大使是一個大專院校的種子計畫，成員來自台灣各大校園的學生，主要任務就是推廣「AWS 雲端教育資源」。基本上跟「教育」有關的任務都和大使有關，以下就簡單列舉幾個大使任期內的實際任務：\n與校方接洽，舉辦大型活動，例如: University AWSome Day (UAD) 運營證照陪跑計畫，協助對雲端科技有興趣的人士考取 AWS 證照 舉辦技術工作坊，教學對雲端科技有興趣的人士使用 AWS 舉辦職涯講座，解惑 AWS 企業文化以及職涯上的諮詢 大使總共分為三個職能：\n活動規劃 行銷推廣 技術支援 (Me) 但實際加入到大使內部後，其實還有很多額外的專案可以去參與，這些專案都是自由參加的\nAWS Demand Generation Representative (DGR): 與 AWS BD 正職參與業務開發，進行陌生開發，尋求商業上的 Opportunity AWS Cloud Support Mentorship (6th 開始才有): 與 AWS Cloud Support Engineer 們學習 AWS 服務、作業系統和網路協議 AWS Dev Team (6th 開始才有): 開發大使內部工具 (TPET) 來解決大使業務的痛點 Creative Project: 展現你的創新及創意能力！第六屆的大使開發了 AWS AI 心理測驗 各個大大小小的活動支援，像是我有參與： 2024 AWS Summit Taipei AWS Professional Service Team x 台電 GenAI Hackathon (這活動我有特別寫一篇文章 → 連結) 還有好多… 就不一一列舉 我從 2024 年 3 月起，因為在 eCloudValley 實習時有寫學習日誌，結果後來變成一個習慣延續到今日，如果想看我 3 月以來在大使的詳細故事不妨也可以逛逛我的 Notion\n前期 - 2024 年 3 月份 ~ 5 月份 在 6th 雲端大使任職前期 (2024/03 ~ 2024/05/31)，我其實身兼四個身份，包含:\n輔仁大學學生，要代表輔大參賽雲端運算比賽 AWS Educate 雲端大使 eCloudValley Cloud Engineer Intern eGroupAI 的雲端諮詢和研究 事情是很多，但其實不會累，反而是很樂在其中。\n當時學校我只有選修一門課那門課是學 GCP，在 eCloudValley 實習當然就是持續精進 AWS 還有 Azure，然後當時有遠端接 eGroupAI 的一些 case，當時接的 case 都是雲端技術方面的諮詢和研究，所以我才會說很樂在其中。\n此外，當時還有一場雲端運算的比賽要比，比賽是用 GCP，很可惜最後是在以第六名收尾 (好像全部有 30 位上下的參賽者吧，不太記得確切數字)\nOnboarding (3 月份) 在上任典禮，很開心總經理 Robert 親自蒞臨現場和學生們互動和交流，很難得能夠有這種機會跟高層領導面對面交流\n雲端大使部分，因為我是 Group3 Team Lead ，我們這組使用了 Slack + Jira 來協作，所以先前我花了蠻多時間建置自動化通知系統和學習 Jira，除此之外為了確保我組內的成員可以順利協作，也花了一些時間去準備一些 Collaboration 的文件。\n證照陪跑計畫 而這期間 ( 3 月份 ) 大使們主要任務之一就是籌備證照陪跑計畫，其實最初大使這邊只是知道說這屆目標是要推廣證照，基於這個目標，我才發想出證照陪跑計畫這個概念然後畫出運作的架構圖，後續再加上其他 Team Lead 以及組員們的優化，這個陪跑計畫就成形了！而也為了推廣這個資源，各個大使也是積極與各大校園接洽。而我們這組除了舉辦了 04/19 AWS 證照陪跑計畫說明會之外也舉辦了 05/24 的證照課程，我當時負責講解 VPC 以及 Networing 的基本概念。\n除此之外，大使們為了證照陪跑創立了一個 Discord 社群，這個社群是為了讓大家可以在裡面討論證照相關的問題，以及讓大使們可以在裡面公告陪跑計畫相關的資訊。我在這個社群裡面替很多人解答了不少問題，也因此我自己也學到了不少新知識，這個社群也是我們大使們在這期間的一個重要資源。\n師大 UAD (03/19) 在師大 UAD，我身為雲端大使，主要任務是擔任聯繫窗口之一，協助回答師大的老師們在通訊軟體上提出的問題並協調大使內部處理任務，當時我們組的兩位成員 Richie 和 Queena 就在這場活動中分別宣傳了 GenAI Hackathon 以及證照陪跑計畫這個資源！\nDev Team 成立 (04/22) 在大使計劃運營期間，其實便有發現一些業務上的痛點和難題要解決，像是：\n需要寄送大量的客製化信件 需要製作大量參與證明 也因此我籌組了 Dev Team，團隊使用了 Serverless 服務並在微服務架構下打造出一個客製化信件寄信系統 (TPET)，相比以往的手動作業，節省至少 80% 以上的時間，而一個月的平均花費只要 1.5 美金\n如果對 Dev Team 誕生的故事有興趣歡迎看這篇文章: Notion - AWS Educate Dev Team 如何誕生的\nGenAI Hackathon (3月 ~ 5/19) 還有一個大活動就是 GenAI Hackathon，大使們協助支援宣傳，同時也有幾位大使對這比賽也很有興趣，於是就自行組隊，隊名就叫做「大使夢之隊」哈哈，參加這場活動學到好多 GenAI 的技術，賽前我參加了不少工作坊，可惜我們最終敗給一個強敵，以第二名收尾。感謝隊長 Yuna，還有隊友們: Richie, Eason, Toby 的付出，是個很寶貴和難忘的比賽經驗！\nGenAI Hackathon 比賽詳細的內容歡迎看這篇文章: 學生時期的最後一場比賽: 2024 GenAI Hackathon 比賽紀錄 | Shiun\n輔仁大學 x AWS Educate: 技術人在 AWS 的職涯開箱 (5/24) 這其實是一個職涯講座 + GenAI 技術工作坊，主講者是 AWS Solutions Architect - Ginny 學姊，當初我負責與輔大這邊的教授接洽，確認好時間和場地後，後續由我們組員 Queena 來進行活動規劃。這場真的好感謝 Ginny 學姊，Ginny 學姊和我一樣也是在輔仁大學資管系畢業的，當天她回來母校演講，為學弟妹提供了職涯分享以及教學 AWS GenAI 的服務，這場工作坊最後獲得了 4.8/5 的滿意度，而且後續教授也對於這場工作坊給予極高的評價，希望後續還能跟 AWS 這邊合作！\n補充 1: GenAI Hackathon 的其中一場賽前工作坊是 Ginny 學姊帶的，當時有做了一份筆記紀錄當時的工作坊實作過程: Notion - 20240427 實習日誌\n補充 2: 在職涯方面我自己也受到 Ginny 學姊很大的幫助！在輔大這場工作坊我也是受益者，當天我身為活動籌辦方之一但也默默在台下當聽眾，在日後的求職過程中，Ginny 學姊也給予了我一些建議，真的很感謝她\nCloud Support Mentorship 對於 Tech 職能的大使有一個很棒的一個資源就是 Cloud Support Engineer (CSE) Mentorship，內容包含了一系列的基礎課程 (AWS、Linux、Networking)，而且在 Dev Team 開發 TPET 的初期，CSE 主動告知我們過去有一些開發上的經驗可以跟我們分享，或是我們有什麼問題也可以諮詢，這對於 Dev Team 來說，在開發初期便奠定了良好的基礎。除此之外，在後續有一場 Spotify 工作坊要教學 CloudFront，正好這當中有一位 CSE - Richard 是 CloudFront 的專家，在開發工作坊的過程中我們遇到了一些 CloudFront 的問題，Richard 便主動邀請我們可以去辦公室向他詢問 CloudFront，很感謝他犧牲下班時間來解答我們的問題。除此之外，Spotify 工作坊當天 CSE 們也有到場，也為現場的工作坊學員們提供了很多諮詢和建議。\n我在 20240614 的日誌中，有寫了一篇關於這段 Mentorship 的心得分享: Notion - 20240614 - AWS Cloud Support Mentorship Program 心得分享\n中後期 - 2024 年 6 月份 ~ 10 月份 在 6th 大使中後期，我從輔仁大學畢業了，也結束了為期三個月的 eCloudValley 的實習計畫\nAWS Professional Service x 台電 (6月初~中旬) 這個活動其實為期數天，包含了非常多場工作坊和講座，我總共支援了 4 場，主要負責活動現場的後勤、協助客戶在工作坊中遇到的問題，也協助準備了某一場的工作坊內容並在現場擔任助教教學他們使用 AWS 技術。在這 4 場活動中我也得到了幾個很大的收穫:\n學習到逆向工作法，從客戶的需求為出發，逐步逆向規劃，確保每個開發階段都與客戶需求緊密對應 認識 AWS Professional Service Team 的 CCIE 網路專家 Edward！我自己對 Networking 這領域很有興趣，那時候和 Edward 聊了很多，他也解答了很多我 Direct Connect, IPv6 和一些實體機房的很多疑惑 和 Professional Service Team 的 Joe 聊了很多雲端方面的職涯建議，得到了很多寶貴的經驗談，我也很謝謝 Joe 信任我，能夠給我機會讓我能夠替客戶解答問題 AWS Educate 陪跑計畫 - 雲端串流挑戰：復刻 Spotify 的技術旅程 (07/05) 這場是由我們 Group3 行銷職能的組員 Jean 想到的主題，把原本比較枯燥的技術內容包裝成：「用現代方法去解決 Spotify 初期遇到的技術難題」。這場工作坊的最後我們教學了 Lambda@Edge，也因此難度提高不少。\n而開發工作坊期間，我們也受到了 Cloud Support Engineer - Richard 的協助，在下班時間讓我們到辦公室諮詢 CloudFront 的問題，而且當天有三位 Cloud Support Engineer 到場協助，工作坊結束還看到學員排隊去諮詢 Cloud Support Engineer，很感謝他們當天的協助，也謝謝 Group3 的大家，我們這場工作坊最後獲得 4.9/5 的滿意度！\n2024 AWS Summit Taipei (07/23~07/24) 一年一度在台灣最盛大的一場 AWS 活動莫過於為期兩天的 AWS Summit 了！但好可惜第二天因為颱風假而取消了\nDay1: 支援 Card Clash 3D 卡牌遊戲攤位\n這是一個透過玩遊戲的方式學習 AWS 雲端架構的卡牌遊戲，所以我們大使事前需要先做好特訓，玩過並且熟悉這個遊戲，而我就很自然的把所有架構都破關了哈哈，一方面是自己也在學習，另一方面是我希望自己提前對每個架構都做好充足的了解，當天來攤位體驗的人我才能好好向他們介紹各個架構。\n當天來體驗的人各式各樣，有學生、業界的工程師、也有比較年長的人士來體驗，當天從體驗者那邊得到了好多正面回饋，都說我講解架構和服務講解得簡單易懂，這真的給我好大的成就感，真的是很喜歡這種直接面對面的交流。\nDay2: Developer Lounge Panel Discussion 與談人之一\n第二天其實很可惜，原本是有這個榮幸能夠在這個舞台成為一個 Panel Discussion 的與談人之一，但因為颱風而取消了\nAWS AI 心理測驗 AWS 心理測驗是由 Group1 的大使 Harry 一開始發想出來的 idea，整個心理測驗包含題目和 UI/UX 設計由 Group1 的大使們設計與開發，而這個項目有和我們 Group3 的 SageMaker 工作坊合作。\n我在這過程中也協助開發 API 並且確保雲端安全，其實心理測驗釋出後，有發生一個小插曲！我們的 AWS 帳號某些服務被鎖了，不確定是不是被攻擊所導致，但多虧當時後端這塊我有協助落實 IaC，在發現問題後，才得以迅速地部署同樣規格的後端到另一支帳號，而這寶貴的一課沒有任何財損！但卻讓我們大大提升了資安意識，給我們很大的警惕，也因此後續我們落實 MFA 登入，也使用了 GuardDuty，我也在後續設置了多道 Budget 並串接 Slack 確保及時通知，避免我們因任何攻擊導致帳單突然爆開。\n這個心理測驗釋出後迴響非常好，後面為了控制成本，我協助在 API 那部分加入了限流政策，我很榮幸能夠和 Group1 有這次的合作機會，這也是大使第一次的跨組別合作的專案！\n2024 AWS Community Day Taiwan (09/28) 去年 (2023) 我是底下的聽眾，沒想到今年有這個榮幸我成為一個講者，帶領「生成式 AI 工作坊：用 SageMaker 打造 AI 心理測驗 」\n我們這個工作坊與 AWS AI 心理測驗平台合作，打算教大家使用 SageMaker 這個服務來微調出一個「可以分析心理測驗結果並且以客製化語氣回答的語言模型，並實踐 MLOps」\n這個工作坊也是我任職大使計劃以來最難、成本最高的一場工作坊，難點包括：\nSageMaker 這服務很龐大且複雜，我們過去完全沒有專案實作經驗 這相當吃重機器學習的理論，不是單單串接 API 或是在 AWS Console 上面按一按那麼簡單 相當耗時間，從資料準備、處理和模型訓練都要耗費大量時間 很難預期結果，每次 Fine-tune 完都會發現結果不如預期 因為要確保工作坊的難度不能太難，所以被綁在 SageMaker 內和 AWS 的生態系內，在開發時比較綁手綁腳，沒辦法使用其他外部工具，引入額外的外部工具會讓我更好開發，但可能會使工作坊變得更複雜 為了克服上面提到的難點，我先是把台大李宏毅老師課程看完確保對機器學習理論了解，然後我再去 Udemy 買了兩個課程，學習 SageMaker 如何使用以及 MLOps 的知識，最後麻煩 Boyi (她是大使媽媽) 幫我們開 Workshop Studio 練習 SageMaker 實際操作\n而 GenAI 這塊我想大家都知道是個相當燒錢的領域，若沒有這次這個機會，我自己應該根本沒機會碰到 SageMaker，這次的工作坊對我來說也是很寶貴的學習機會\n而我們這場工作坊在 AWS Comminity Day 官方網站的線上報名階段就額滿了，Community Day 當天也是在外面排起隊來，最後是整個教室都坐滿了，但因為教室空間有限，對於當天在外面排隊因額滿沒辦法進來參與的參加者們感到很抱歉\n最後這場工作坊得到了好評，得到了 4.9/5 的滿意度，這場工作坊很關鍵的一部分是 Richie 在 AI 領域的高度專業和研究，可說是我們這場工作坊的核心！\n工作坊的 GitHub 連結: https://github.com/aws-educate-tw/aws-educate-sagemaker-workshop\nAWS User Group Taiwan 中部小小聚 (10/19) 這場很榮幸我和 Yuna 可以到台中逢甲大學作為 User Group Taiwan 的講者，這天由我和 Yuna 帶領技術工作坊：「雲端串流挑戰：復刻 Spotify 的技術旅程 2.0」\n這場工作坊是從 2024/07/05 陪跑計畫中所舉辦的「雲端串流挑戰：復刻 Spotify 的技術旅程」工作坊改良而成的，主要做了以下改進:\n降低整體工作坊的難度，把 Lambda@Edge 那部分刪除，以確保難度控制在 Level 100 刪除「 SQL 相關的操作」，開發了一個管理後台，讓工作坊學員可以更專注在學習 CloudFront 針對 CloudFront 各個 Components 講解了更多細節和其他雲端架構 這場活動還遇到了當初在 AWS Community Day 有參與 SageMaker 工作坊的參加者，我們從中也再次得到她對 AWS AI 相關服務的反饋，聽 Yuna 說這位參加者後來自己辦了 AWS 帳號，自己也再次去探索 AWS AI 服務！\n每次聽到正面反饋，知道自己的能力能夠幫助到他人的感覺真的很棒！\n這場工作坊很榮幸我們得到了 5/5 的滿意度，真的很謝謝和我一同奮鬥兩屆大使計劃的戰友 - Yuna 一起帶領這次的工作坊，也很感謝 User Group 協助處理場地事宜！\n如果有興趣學習 CloudFront，歡迎看看這個我們的 Notion 教材唷: Notion - 20241019 中部小小聚 - CloudFront 101：復刻 Spotify 的技術旅程\n結語 從 2023 年夏天，正式加入第五屆 AWS Educate 雲端大使，然後就一路待到大學畢業，能在學生生涯的尾聲加入到這個計劃真的很開心，也從中成長了許多，過程中得到了好多好多貴人的幫助，幫助我的人實在太多了，實在很難一一列舉出來，每個人都在我的成長旅程中，留下了無可取代的足跡。\n身邊有許多人跟我說你做這個又沒錢，我每次面對這樣的質問都不知道該如何回答他們，因為打從一開始我就是抱著滿腔對雲端技術的熱情進來，從沒有想過要用錢或是等價的東西去交換我要為這個團隊做多少事情。\n呼應文章開頭，其實在學生時期我就已經對自己的職涯發展有了初步的雛形，在參與這段大使計劃的過程中，我持續探索自我，更加認識到自己，對未來的職涯方向變得越來越清晰，就好像 Diffusion Model 生成圖像一樣，隨著一步一步地去噪，圖像也越來越清晰。\n「這裡資源很多，只怕你不去學而已。」\n\u0026ndash; Learn and Be Curious\nNext Step 若真的有讀者看到這裡，很感謝你耐心花了那麼長的時間閱讀。\n我的學生職涯已結束，但我仍會保持好奇心持續學習，我現在必須步入職場，開始人生的下一段職涯了，我對 Networking, Hybrid Cloud 還有 Kubernetes 很有興趣，所以這陣子在準備 AWS Certified Advanced Networking - Specialty (ANS) 還有 Certified Kubernetes Administrator (CKA) 這兩張證照。接著 12/23 退伍後就要積極來找工作了，如果有任何讀者有任何「雲端運算 (AWS/GCP/Azure)」而且是「Customer Facing Role」的職位也懇請能夠推薦給我，歡迎各方的任何指教或是建議！\nLinkedin: https://www.linkedin.com/in/shiunchiu/ Email: shiunchiu.me@gmail.com ","permalink":"https://shiun.me/blog/my-growth-journey-as-an-aws-educate-cloud-ambassador/","summary":"(原本沒打算寫那麼多，結果每個故事的前因後果，一個連結一個，就越寫越多了)\n最近我從第六屆 AWS Educate 雲端大使計劃畢業了！第六屆大使計劃任職期間真的非常充實且充滿挑戰，我也在這段旅程中成長了非常非常多。\n這篇文章會從我踏上資訊科技之路開始說起，一直到我加入 AWS Educate 雲端大使計劃，再一一回顧我在第六屆 AWS Educate 雲端大使中的一些成就和重要活動，最後也會分享一些我在這段旅程中的收穫和成長，以及我的近期未來規劃。\n我和 AWS 的淵源，我又是怎麼一路成為 AWS Educate 雲端大使 「我覺得我很幸運，我算是在很早期就對於職涯目標很明確，而且到越後期越明確。」\n講到這個，回憶起高中時，學校為了我們選組還有選大學科系有做了兩次興趣測驗，我只知道測驗結果顯示我的興趣鑑別度很明確，而且我在資訊管理學系超級高分，重測一次的結果也是一樣，也因此我很早的志願就是「資訊管理學系」，所以學測完申請學校時，每個學校都填資訊管理學系。後來就進入了輔仁大學讀資管系，現在回頭看，真的沒選錯路，完完全全選對路。\n2022 踏入職場後與 AWS 的第一次接觸 我是從大二開始進入職場實習的，這間公司叫做 eGroupAI，主要產品有兩個，分別是人臉辨識系統和 SaaS 企業管理平台，當時我就是以一個菜鳥實習生身份進入，也是第一次見證到真正業界的開發流程，當時的公司主要使用 Spring Boot，如果要加入公司的正式開發的行列，需要歷經一個實習生考試，沒有考過就是直接掰掰，所以當時白天實習，晚上回到家要瘋狂讀課程和寫作業，還好最後考過了。\n加入正式開發的行列後，第一件事情就是要配置好開發環境，光是裝 IDE, JDK, 配置一堆有的沒的就可以用掉一整個工作天，我當時跟 AWS 的第一次接觸，就是主管叫我去安裝 AWS CLI，配給我一個 IAM User 的 Access Key ID 和 Secret Access Key (簡稱 AKSK)，然後輸入一些指令，當時我其實也不知道那是什麼，問了主管這個是什麼東西，他說因為開發會有一段會經過雲，後端有用到 DynamoDB，這樣你在本地開發時才有權限去處理那邊的東西。\n而也隨著我在學習的過程，我開始對整個軟體的運作流程以及架構逐漸明朗，也因此我與雲端的淵源越來越深，認識的也越來越多。2022 年的某天，我參加了 AWS Educate 舉辦的工作坊，而且是辦在 AWS 辦公室裡面，正好可以去參觀一下。當時在工作坊尾聲時，有一個最大獎，只要回答出今天講的服務的一個實際應用案例，就可以拿走，正好我當時開發時就有處理過 S3 相關的東西，所以我腦海馬上蹦出我在開發上的實踐，最後這個大獎就被我拿走了，當時這個小小成就感影響我蠻大的，讓我有繼續探索雲端技術的動力。\n不夠自信讓我差點卻步申請 AWS Educate 雲端大使計劃 隨後就是我迷上了雲端技術，當時 AWS 大大小小的活動我都一定會參加，但是 AWS 的活動都舉辦在平日，為了參加活動我都必須翹課，而且排班時都要避開 AWS 活動日，而當時我對容器化很感興趣，所以每個活動的下午分場議程我都專挑一些 Modernization 或是 Container 相關的主題，那時候有一個 Solutions Architect (SA) 他叫做 Kerrigan ，我特別喜歡他的演講風格，而且他演講的主題多半都和我感興趣技術有關，那時候我剛開始經營 LinkedIn，當時我認為建立關係是一個很謹慎且重大的事，很怕一個菜雞學生跟大神建立關係會被拒絕，某次演講結束後我就寫了一段訊息然後鼓起勇氣去建立關係，這是我第一次跟原本不認識的人建立關係，而沒多久後我看到被接受邀請的通知就超開心，而且 Kerrigan 也有給予我一些正面回應。現在回頭看，雖然好像只是簡單的建立關係，但對當時的我來說是相當大的鼓舞，這是能讓我繼續往雲端技術探索動力來源之一。","title":"一起回顧我的 AWS Educate 雲端大使成長之旅"},{"content":"最近正在準備 AWS ANS 證照剛好學到 AWS Site-to-Site VPN，也因此實作練習對我來說很重要所以打算寫這篇文章，一方面記錄實作過程一方面留著可以作為教學文章供他人參考\n這篇文章將教學如何在 AWS 上建立 Site-to-Site VPN，我們會使用 Libreswan 來配置 VPN Server，這樣就可以在 AWS VPC 和 Data Center (本文示範會在 N. Virginia Region 中建立 VPC 來模擬 Data Center) 之間建立一個安全的 VPN Tunnel，讓兩者可以互相通信\n一、創建 VPC (Oregon Region) 首先我們先創建架構圖右邊 AWS 的 VPC，因此我切換到 Oregon Region，並且依照下圖配置 VPC 以及 Subnet\n二、配置 Route Table (Oregon Region) 然後創建 Route Table，並和上一步創建的 Subnet 建立 Association\n我們要修改 Subnet 的 Association，確保該子網與我們剛才創建的路由表關聯在一起\n如下圖配置 Route Table association\n三、創建 EC2 Instance (Oregon Region) 接著進入 EC2 Console 頁面，現在要在剛才的 Subnet 啟動一台 EC2 Instance，若沒提到的地方可以保持預設\n名稱: EC2-at-AWS AMI 選擇 Amazon Linux 2023 AMI 然後在 Network settings 處依照下圖配置 四、創建 Data Center ( 這裡以 N. Virginia 的 VPC 來模擬 Data Center ) 因為我們沒有實際的資料中心，所以我們就以 N. Virginia Region 中的 VPC 來模擬我們的 Data Center\n這裡需要特別注意的是我們要創建 Public Subnet，因為後續在這裡的 EC2 Instance 需要一個 Public IPv4 地址作為我們 Customer Gateway (CGW) 使用\n五、創建 EC2 Instance (N. Virginia Region) 現在需要創建一個 EC2 Instance 作為 VPN Server，這台 Instance 會安裝 VPN 軟體 (Libreswan) 作為 CGW 然後與 AWS VGW 對接形成一個 VPN Tunnel\n進入 EC2 Console 頁面，依照下方配置，其他可以保持預設\n名稱: VPN-EC2-at-DC AMI 選擇 Amazon Linux 2023 AMI 創建一個 Key pair 並保存好私鑰，一會我們需要連線至 Instance 然後在 Network settings 處依照下圖配置，一定要記得 Assign Public IP 六、創建 VGW (Oregon Region) 現在回到 VPC Console (Oregon Region) 我們要來配置 VGW\n創建這個很簡單，如下圖:\n創建好之後要將其 Attach 到 VPC\n七、創建 CGW (Oregon Region) 現在我們要創建 CGW，一樣是在 Oregon Region，創建 CGW 最重要的就是 Public IP! 這裡我們是使用別的 Region 的 VPC 來模擬了 Data Center，所以我們要去 N. Virginia Region 複製 EC2 Instance 的 Public IPv4 位址\n創建好 CGW 如下圖:\n八、創建 Site-to-Site VPN connection (Oregon Region) 現在已經創建好 VGW 和 CGW，我們就要來把這兩個 Gateway 連接起來，因此就需要創建 Site-to-Site VPN connection\n如下圖配置\n創建後需要等待約 3~5 分鐘\n九、修改 Route Table (Oregon Region) 為了確保 AWS VPC 中的流量知道什麼狀況下要將流量引導至 VGW，所以必須修改 Route Table，如下圖:\n十、下載配置檔案 我們準備要連線至 EC2 Instance (N. Virginia) 配置 VPN 軟體了，在這之前，需要先去 Site-to-Site VPN Connection 頁面中 (Oregon) 下載 Configuration file\n十一、配置 VPN Server 準備好你的 .pem 檔案，連線至 EC2\n$ chmod 400 /path/to/your-key.pem $ ssh -i /path/to/your-key.pem ec2-user@54.32.10.1 , #_ ~\\_ ####_ Amazon Linux 2023 ~~ \\_#####\\ ~~ \\###| ~~ \\#/ ___ https://aws.amazon.com/linux/amazon-linux-2023 ~~ V~\u0026#39; \u0026#39;-\u0026gt; ~~~ / ~~._. _/ _/ _/ _/m/\u0026#39; 連線進去後，要來安裝 Libreswan，首先要修改軟體源配置文件，確保我們下載得到 Librwswan\nsudo vi /etc/yum.repos.d/fedora.repo 指令解釋: 這條指令的功能是用來編輯 Linux 系統中的 /etc/yum.repos.d/fedora.repo 文件。這是一個典型的場景，適合於 Fedora 或使用 yum 軟體包管理器的其他 Linux 發行版。 關於 vi 的操作方式如下，後面不會再重複說明此操作方式:\n按下 i 進入 INSERT 模式 貼上下方內容 按下 Esc 輸入 :wq [fedora] name=Fedora 36 - $basearch #baseurl=http://download.example/pub/fedora/linux/releases/36/Everything/$basearch/os/ metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-36\u0026amp;arch=$basearch enabled=0 countme=1 metadata_expire=7d repo_gpgcheck=0 type=rpm gpgcheck=1 gpgkey=https://getfedora.org/static/fedora.gpg skip_if_unavailable=False 執行以下指令來安裝 Libreswan:\nsudo dnf --enablerepo=fedora install libreswan -y 指令解釋: 這條指令 sudo dnf --enablerepo=fedora install libreswan -y 用於在基於 Fedora 的 Linux 發行版上安裝 Libreswan 軟體包。 打開 /etc/sysctl.conf 然後加上以下內容:\nsudo vi /etc/sysctl.conf 然後執行指令 sysctl -p ，如下:\n$ sudo sysctl -p net.ipv4.ip_forward = 1 net.ipv4.conf.default.rp_filter = 0 net.ipv4.conf.default.accept_source_route = 0 指令解釋: 指令 sysctl -p 用於加載並應用系統內核參數的配置文件。修改了 /etc/sysctl.conf 後，運行 sysctl -p 可以立即應用這些內核參數，無需重啟系統。 檢查 /etc/ipsec.conf 檔案最後面的 include /etc/ipsec.d/*.conf 前面的 # 已被移除，若沒有移除，請使用 vi 開啟該檔案後將 # 移除，你可以輸入以下指令來查看 /etc/ipsec.conf :\nsudo cat /etc/ipsec.conf 創建一個新檔案 /etc/ipsec.d/aws.conf :\nsudo vi /etc/ipsec.d/aws.conf 接著你務必要參照你在前面下載的 Configuration File 去配置 /etc/ipsec.d/aws.conf ，除此之外由於 Libreswan 有一些配置不支援，因此還有一些內容需要小修改，包括:\n移除 auth=esp phase2alg=aes128-sha1;modp1024 修改為 phase2alg=aes_gcm ike=aes128-sha1;modp1024 修改為 ike=aes256-sha1 leftsubnet=\u0026lt;LOCAL NETWORK\u0026gt; 的 \u0026lt;LOCAL NETWORK\u0026gt; 修改為 Data Center 的 CIDR (我的例子是 N. Virginia Region 上的 VPC 來模擬，也就是 192.168.0.0/16 ) rightsubnet=\u0026lt;REMOTE NETWORK\u0026gt; 的 \u0026lt;REMOTE NETWORK\u0026gt; 修改為 AWS VPC 的 CIDR (我的例子是 Oregon Region 上的 VPC，也就是 10.0.0.0/16 ) 綜合以上，我會在 /etc/ipsec.d/aws.conf 中加入以下內容:\nconn Tunnel1 authby=secret auto=start left=%defaultroute leftid=54.227.52.173 right=35.81.211.175 type=tunnel ikelifetime=8h keylife=1h phase2alg=aes_gcm ike=aes256-sha1 keyingtries=%forever keyexchange=ike leftsubnet=192.168.0.0/16 rightsubnet=10.0.0.0/16 dpddelay=10 dpdtimeout=30 dpdaction=restart_by_peer 創建一個新檔案 /etc/ipsec.d/aws.secrets :\nsudo vi /etc/ipsec.d/aws.secrets 務必要參照你在前面下載的 Configuration File 去配置 /etc/ipsec.d/aws.secrets\n以我的為例，我會在 /etc/ipsec.d/aws.secrets 加入以下內容:\n54.227.52.173 35.81.211.175: PSK \u0026#34;TFIcmolpZYe4_y0zhjJiSaetWD8F4VZr\u0026#34; 終於我們配置好了，可以啟動 Service 囉:\nsudo systemctl start ipsec.service 啟動 IPSec Service 後，輸入以下指令檢查狀態:\n$ sudo systemctl status ipsec.service ● ipsec.service - Internet Key Exchange (IKE) Protocol Daemon for IPsec Loaded: loaded (/usr/lib/systemd/system/ipsec.service; disabled; preset: disabled) Active: active (running) since Mon 2024-10-21 14:15:03 UTC; 27s ago Docs: man:ipsec(8) man:pluto(8) man:ipsec.conf(5) Process: 28237 ExecStartPre=/usr/libexec/ipsec/addconn --config /etc/ipsec.conf --checkconfig (code=exited, status=0/SUCCESS) Process: 28239 ExecStartPre=/usr/libexec/ipsec/_stackmanager start (code=exited, status=0/SUCCESS) Process: 28677 ExecStartPre=/usr/sbin/ipsec --checknss (code=exited, status=0/SUCCESS) Process: 28682 ExecStartPre=/usr/sbin/ipsec --checknflog (code=exited, status=0/SUCCESS) Main PID: 28693 (pluto) Status: \u0026#34;Startup completed.\u0026#34; Tasks: 2 (limit: 1112) Memory: 8.7M CPU: 523ms CGroup: /system.slice/ipsec.service └─28693 /usr/libexec/ipsec/pluto --leak-detective --config /etc/ipsec.conf --nofork Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: adding UDP interface lo 127.0.0.1:4500 Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: adding UDP interface lo [::1]:500 Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: adding UDP interface lo [::1]:4500 Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: loading secrets from \u0026#34;/etc/ipsec.secrets\u0026#34; Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: loading secrets from \u0026#34;/etc/ipsec.d/aws.secrets\u0026#34; Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: \u0026#34;Tunnel1\u0026#34; #1: initiating IKEv2 connection Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: \u0026#34;Tunnel1\u0026#34; #1: sent IKE_SA_INIT request to 35.81.211.175:500 Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: \u0026#34;Tunnel1\u0026#34; #1: sent IKE_AUTH request {cipher=AES_CBC_256 integ=HMAC_SHA1_96 prf=HMAC_SHA1 group=MODP2048} Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: \u0026#34;Tunnel1\u0026#34; #1: initiator established IKE SA; authenticated peer using authby=secret and ID_IPV4_ADDR \u0026#39;35.81.211.175\u0026#39; Oct 21 14:15:03 ip-192-168-2-57.ec2.internal pluto[28693]: \u0026#34;Tunnel1\u0026#34; #2: initiator established Child SA using #1; IPsec tunnel [192.168.0.0-192.168.255.255:0-65535 0] -\u0026gt; [10.0.0.0-10.0.255.255:0-65535 0] {ESPinUDP=\u0026gt;0xca93ac2d \u0026lt;0x2f788c\u0026gt; lines 1-28/28 (END) 按下 q 即可跳出\n十二、測試是否可以 Ping 到 Oregon Region 上的 Private EC2 Instance 我們已經建立好 Site-to-Site VPN，若配置正常，Data Center 和 AWS 是可以彼此互相通信的\n在這裡我們會使用 N. Virginia Region 中的這台 EC2 去 Ping 看看 Oregon Region 上的 Private EC2 Instance，所以請去 Oregon Region 複製其 Private IPv4 address:\nping 10.0.0.131 可以看到我們成功 Ping 到 Oregon 上的 EC2 Instance 了！\n十三、Pricing 透過這篇文章，我們學會如何在 AWS 建立 Site-to-Site VPN，最後練習完也別忘記要清理資源避免衍生出不必要的花費。而最後這裡我想稍微介紹一下 Site-to-Site VPN 收費，但最新資訊仍以官方為主 (連結)\nAWS Site-to-Site VPN 的收費方式如下：\n連線費用：當你建立一個 Site-to-Site VPN 連接時，AWS 會按 每小時計費，只要連線處於啟用狀態，無論有沒有數據傳輸都會計算費用。例如，在 Ohio Region，這個連線的費用是 每小時 $0.05，一個月持續啟用下來，大約是 $36 美元​​。 數據傳輸費用：數據傳輸會有額外的費用。前 100 GB 的傳輸是免費的，但超過 100 GB 後，傳輸費用是 每 GB $0.09。例如，如果你一個月傳輸了 1,000 GB，則你需要支付超出免費流量的 400 GB 傳輸費用（$36）​。 加速 VPN（選擇性）：如果你啟用了 加速 Site-to-Site VPN（利用 AWS Global Accelerator），則會有額外的費用。除了 VPN 連線費用，還包括每個 Global Accelerator 的費用（每小時 $0.025），以及 Data Transfer Premium（依照地區與傳輸方向計算）​​。 綜合來看，一個基本的 AWS Site-to-Site VPN 連線每月費用可能為 $72，而加速 VPN 的費用更高，可能達到 $123 一個月，視數據量和是否啟用加速功能而定​。\n相關連結 AWS VPN Pricing libreswan ","permalink":"https://shiun.me/blog/how-to-set-up-a-aws-site-to-site-vpn-with-libreswan/","summary":"最近正在準備 AWS ANS 證照剛好學到 AWS Site-to-Site VPN，也因此實作練習對我來說很重要所以打算寫這篇文章，一方面記錄實作過程一方面留著可以作為教學文章供他人參考\n這篇文章將教學如何在 AWS 上建立 Site-to-Site VPN，我們會使用 Libreswan 來配置 VPN Server，這樣就可以在 AWS VPC 和 Data Center (本文示範會在 N. Virginia Region 中建立 VPC 來模擬 Data Center) 之間建立一個安全的 VPN Tunnel，讓兩者可以互相通信\n一、創建 VPC (Oregon Region) 首先我們先創建架構圖右邊 AWS 的 VPC，因此我切換到 Oregon Region，並且依照下圖配置 VPC 以及 Subnet\n二、配置 Route Table (Oregon Region) 然後創建 Route Table，並和上一步創建的 Subnet 建立 Association\n我們要修改 Subnet 的 Association，確保該子網與我們剛才創建的路由表關聯在一起\n如下圖配置 Route Table association\n三、創建 EC2 Instance (Oregon Region) 接著進入 EC2 Console 頁面，現在要在剛才的 Subnet 啟動一台 EC2 Instance，若沒提到的地方可以保持預設","title":"AWS Site-to-Site VPN with Libreswan 建置教學 (Step-by-Step)"},{"content":"因為我最近在開發寄信系統，因此理解電子郵件寄送的流程和原理是很重要的，為了方便理解，所以我會用真實世界中我們去郵局寄信來比喻電子郵件的寄送流程，這麼做是為了幫助理解，所以比喻那部分很難做到 100% 完全和技術細節對齊\n用真實世界在郵局寄信，理解電子郵件寄送的流程 圖片取自: 維基百科\n為了更好地理解電子郵件的寄送過程，我們可以把它比作郵局寄信的過程。假設 Alice 給 Bob 寫了一封感謝信，然後她將信件帶到郵局處理寄信手續。接下來，我們來看看具體過程:\n創建信件\n郵局寄信: Alice 寫好感謝信，裝進信封，寫上寄件人和收件人的地址，貼上郵票。 電子郵件: 你在郵件客戶端 (例如: Outlook、Gmail) 或透過 API 建立一封郵件，內容包含寄件人 (例如: alice@example.tw) 、收件人 (例如: bob@demodomain.tw) 、主題、內文及其他可能的附件。 信件寄送\n郵局寄信: Alice 把信帶到郵局，然後找郵局的櫃台人員。 電子郵件: 當你點擊「寄送」後，郵件客戶端會將這封郵件交給寄件人的郵件伺服器，稱為 SMTP 伺服器 (Simple Mail Transfer Protocol) 。 SMTP 伺服器處理\n郵局寄信: 郵局的櫃台人員會檢查地址是否正確，然後進行分類。 電子郵件: 寄件人的 SMTP 伺服器會驗證寄件人的身份，檢查信件格式是否正確，並根據收件人的域名 (例如: demodomain.tw) 查找相應的 MX 記錄 (Mail Exchange) 來確定收件人的郵件伺服器位置。 像如果對方沒有設定 MX Record，寄信過去就會出現以下 ERROR，找不到對應的 MX Record DNS 查詢\n郵局寄信: 郵局櫃台人員會根據地址查找對應郵遞區號。 電子郵件: 寄件人的 SMTP 伺服器會透過 DNS (Domain Name System) 查詢收件人域名的 MX 記錄，以獲取負責接收郵件的伺服器的 IP 位址。 信件傳輸\n郵局寄信: 郵差將信件從一個郵局運送到下一個郵局，直到信件到達收件人的郵局。 電子郵件: 寄件人的 SMTP 伺服器使用 TCP/IP 協定，將信件傳輸到收件人的 SMTP 伺服器。這個過程可能會經過多個中繼伺服器，但最終會到達收件人的 SMTP 伺服器。 接收與存儲\n郵局寄信: 收件人的郵局收到信件後，會將信件投遞到 Bob 的信箱中。 電子郵件: 收件人的 SMTP 伺服器收到信件後，會根據收件地址將郵件存儲到相應的收件人郵箱中。這個過程可能涉及過濾垃圾郵件、病毒掃描等安全檢查。 通知收件人\n郵局寄信: Bob 會定期檢查他的信箱，找到 Alice 的信件。 電子郵件: 收件人的郵件伺服器將新郵件存儲到收件人的收件箱中後，會通知收件人的郵件客戶端 (如 Outlook、Gmail) 有新的郵件到達。這通常是透過 IMAP (Internet Message Access Protocol) 或 POP3 (Post Office Protocol) 完成的。 收件人查看信件\n郵局寄信: Bob 拿到信件後，拆開信封閱讀信件內容。 電子郵件: 收件人的郵件客戶端接收到新郵件通知後，會在用戶界面中顯示這封新郵件。當 bob@demodomain.tw 開啟郵件客戶端 (例如: Gmail)時，就能看到你寄送的信件。 通常，第一個電子郵件伺服器並不是電子郵件的最終目的地。當伺服器收到來自用戶端的電子郵件後，會與另一台郵件伺服器進行重複的 SMTP 連線程序。第二台伺服器也會執行相同的程序，直到電子郵件最終到達收件人電子郵件提供者所控制的郵件伺服器上的收件匣。\n圖片取自: 維基百科\n可以將此程序比喻成一封信件從寄件人傳送到收件人的過程。郵差無法直接將寄件人的信件交給收件人，信件可能會先到一個中繼所，然後運送到另一個城鎮的郵局，然後再運送到下一個郵局，如此反覆\u0026hellip;直到信件到達收件人。同樣地，電子郵件透過 SMTP 從一個伺服器傳送到另一個伺服器，直到它們到達收件人的收件匣。\n這個過程看似複雜，但實際上在數秒鐘內就能完成。整個電子郵件系統的設計使得信件能快速、安全地從一個地方傳送到另一個地方。\n參考資料 簡易郵件傳輸通訊協定 SMTP 是什麼？ | Cloudflare 訊息傳輸代理 - 維基百科，自由的百科全書 電子郵件 ","permalink":"https://shiun.me/blog/how-does-email-sending-work/","summary":"因為我最近在開發寄信系統，因此理解電子郵件寄送的流程和原理是很重要的，為了方便理解，所以我會用真實世界中我們去郵局寄信來比喻電子郵件的寄送流程，這麼做是為了幫助理解，所以比喻那部分很難做到 100% 完全和技術細節對齊\n用真實世界在郵局寄信，理解電子郵件寄送的流程 圖片取自: 維基百科\n為了更好地理解電子郵件的寄送過程，我們可以把它比作郵局寄信的過程。假設 Alice 給 Bob 寫了一封感謝信，然後她將信件帶到郵局處理寄信手續。接下來，我們來看看具體過程:\n創建信件\n郵局寄信: Alice 寫好感謝信，裝進信封，寫上寄件人和收件人的地址，貼上郵票。 電子郵件: 你在郵件客戶端 (例如: Outlook、Gmail) 或透過 API 建立一封郵件，內容包含寄件人 (例如: alice@example.tw) 、收件人 (例如: bob@demodomain.tw) 、主題、內文及其他可能的附件。 信件寄送\n郵局寄信: Alice 把信帶到郵局，然後找郵局的櫃台人員。 電子郵件: 當你點擊「寄送」後，郵件客戶端會將這封郵件交給寄件人的郵件伺服器，稱為 SMTP 伺服器 (Simple Mail Transfer Protocol) 。 SMTP 伺服器處理\n郵局寄信: 郵局的櫃台人員會檢查地址是否正確，然後進行分類。 電子郵件: 寄件人的 SMTP 伺服器會驗證寄件人的身份，檢查信件格式是否正確，並根據收件人的域名 (例如: demodomain.tw) 查找相應的 MX 記錄 (Mail Exchange) 來確定收件人的郵件伺服器位置。 像如果對方沒有設定 MX Record，寄信過去就會出現以下 ERROR，找不到對應的 MX Record DNS 查詢\n郵局寄信: 郵局櫃台人員會根據地址查找對應郵遞區號。 電子郵件: 寄件人的 SMTP 伺服器會透過 DNS (Domain Name System) 查詢收件人域名的 MX 記錄，以獲取負責接收郵件的伺服器的 IP 位址。 信件傳輸","title":"以真實世界寄信來比喻，理解電子郵件寄送流程"},{"content":"我遇到了什麼問題 最近我用 SES, S3, Lambda 開發了轉寄信件的功能，簡單介紹一下這功能： 假設我就職於公司的開發部門，我們對外有一個信箱是 dev@example.com 當客戶遇到軟體開發上的問題時寄信到 dev@example.com，開發部門的所有同仁就會在自己的信箱收到此客戶寄來的電子郵件，重點是開發部門的同仁們都可以用自己的帳號回覆該客戶\n而開發好這功能後，我遇到一個問題 — 轉寄出去的信件沒辦法正確顯示內嵌圖片\n關於轉寄信件的程式碼我放在 GitHub 上囉 (連結)，之後有時間會寫一篇 Forward Email 的完整教學，或是也可以關注我的 Notion，我每天都會寫筆記記錄自己的學習過程\n讓我們直接看例子 首先，我登入私人帳號模擬我是客戶，寄信給 test@aws-educate.tw，下圖是當時寄出去的內容: 寄給 test@aws-educate.tw 後，SES 會收到信，然後根據我配置的 Receipt rule，原始郵件檔案會被儲存至我指定的 S3 Bucket\n延續 用 SES, S3, Lambda 實現轉寄信件功能 ，當我嘗試在轉寄出去的信件中內嵌圖片，最後收到轉寄來的信，都會出現下圖的現象：圖片沒辦法內嵌在裡面，倒是跑去附件了\n寫文章時，已經把原本轉寄過來的信件刪了，所以下圖信件主旨跟前一張圖片不一樣不要介意，下圖信件已經詮釋我想表達的現象了 XD\n我起初嘗試的解法 後來下面這篇 Stack Overflow，裡面有人提到 Gmail 還不支援顯示 base64 編碼的圖片 (有待確認，希望有人可以告訴我正確答案)\nAs of January 2020, Gmail still does not support base64 encoded images.\nSource: Add embedded image in emails in AWS SES service\n因此使用 Data URI: \u0026lt;img src=\u0026quot;data:image/png;base64,iVBORw0KGgoAA\u0026quot; alt=\u0026quot;Image\u0026quot;\u0026gt; 嵌入圖片的方式現在可以打消念頭了\n接著可能會有人說，那把圖片上傳到 S3，拿到 Object URL，接著在 \u0026lt;img\u0026gt; 標籤裡面指定圖片網址不就好，例如: \u0026lt;img src=\u0026quot;https://example.com/assets/image.png\u0026quot; alt=\u0026quot;image\u0026quot;\u0026gt; 沒錯，我認為這方法可行，我試過了，但採了無數坑最後失敗告終\u0026hellip; 像是把圖片提取出來儲存好，再修改 Message 的內容比我想像中麻煩\n沒多久後我想了又想，我只是想要轉寄信件為什麼我要想得那麼複雜，不是應該改個 Message 的 From, To 和 Reply-to 標頭就好了，信件內容就保持原封不動，何必徒增額外功夫先把圖片提取出來，然後想辦法把圖片插入原本的內容中\n總而言之，踏過的那些坑，讓我已經對 MIME 研究了一番，所以這篇就來認識一下 MIME 內嵌圖片的原理吧\n認識 MIME 的 boundary 在 MIME 訊息中，boundary 是一個用於分隔不同部分的字串。當一封郵件包含多個部分時，例如文字、HTML、圖片或附件，boundary 就用來標記每個部分的開始和結束。\nboundary 的格式：\nboundary 是一個獨特的字串，通常由兩個連字號 (\u0026ndash;) 開頭，後面跟著一串隨機字元。例如：\n-000000000000d37ee1061ed29569 如何知道 boundary 的開始和結束：\nContent-Type 標頭： 在郵件的 Content-Type 標頭中，會指定 multipart 的類型 (例如 multipart/mixed、multipart/alternative)，並在 boundary 參數中定義 boundary 字串。 例如：Content-Type: multipart/mixed; boundary=\u0026quot;000000000000d37ee1061ed29569\u0026quot; 分隔符： 每個部分的開頭都會有一個分隔符，由兩個連字號 (\u0026ndash;) 和 boundary 字串組成，後面跟著一個換行符 (\\r\\n)。 例如：-000000000000d37ee1061ed29569 結束標記： 郵件的最後一個部分結束後，會有一個結束標記，由兩個連字號 (\u0026ndash;)、boundary 字串和兩個連字號 (\u0026ndash;) 組成，後面跟著一個換行符 (\\r\\n)。 例如：-000000000000d37ee1061ed29569-- 範例：\nContent-Type: multipart/mixed; boundary=\u0026#34;boundary-string\u0026#34; --boundary-string Content-Type: text/plain This is the email body. --boundary-string Content-Type: image/jpeg Content-Transfer-Encoding: base64 ... (base64 encoded image data) ... --boundary-string-- 在上面的例子中：\nboundary-string 是 boundary 字串 -boundary-string 是每個部分的開頭 -boundary-string-- 是郵件的結束標記 實現內嵌圖片核心原理 觀察 Message 在 MIME 的上下文中，message 是指完整的網際網路消息，在這篇文章的話指的就是一封電子郵件\n可以看下方的內容，這是我去 S3 Bucket 把郵件原始檔案 (Message) 下載下來節錄的一個片段，對應了本文開頭圖片的信件內容\n# 以上略 .... Content-Type: multipart/related; boundary=\u0026#34;000000000000d37ee1061ed2956a\u0026#34; --000000000000d37ee1061ed2956a Content-Type: multipart/alternative; boundary=\u0026#34;000000000000d37ee1061ed29569\u0026#34; --000000000000d37ee1061ed29569 Content-Type: text/plain; charset=\u0026#34;UTF-8\u0026#34; test 1047 test 1047test 1047 [image: image.png] --000000000000d37ee1061ed29569 Content-Type: text/html; charset=\u0026#34;UTF-8\u0026#34; Content-Transfer-Encoding: quoted-printable \u0026lt;div dir=3D\u0026#34;ltr\u0026#34;\u0026gt;\u0026lt;div dir=3D\u0026#34;ltr\u0026#34; class=3D\u0026#34;gmail_signature\u0026#34; data-smartmail= =3D\u0026#34;gmail_signature\u0026#34;\u0026gt;\u0026lt;div dir=3D\u0026#34;ltr\u0026#34;\u0026gt;test 1047\u0026lt;br\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;div dir=3D\u0026#34;ltr\u0026#34;\u0026gt;t= est 1047test 1047\u0026lt;br\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;div dir=3D\u0026#34;ltr\u0026#34;\u0026gt;\u0026lt;br\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;div dir=3D\u0026#34;ltr\u0026#34;\u0026gt;\u0026lt;img= src=3D\u0026#34;cid:ii_lzeypikh0\u0026#34; alt=3D\u0026#34;image.png\u0026#34; width=3D\u0026#34;176\u0026#34; height=3D\u0026#34;219\u0026#34;\u0026gt;\u0026lt;b= r\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt; --000000000000d37ee1061ed29569-- --000000000000d37ee1061ed2956a # 以下略 ... 在這裡你要先注意到：我們在外層使用到了 multipart/related ，這是 MIME 的一種訊息格式，用於將一個主文件 (main document) 與其相關資源 (related resources) 組合在一起。\n以我們現在的情境，使用到 multipart/related 就是要拿來內嵌圖片在我們信件內容中\n接著下方其實還有一段內容，是我在信件內的想要內嵌圖片:\nContent-Type: image/png; name=\u0026#34;image.png\u0026#34; Content-Disposition: inline; filename=\u0026#34;image.png\u0026#34; Content-Transfer-Encoding: base64 Content-ID: \u0026lt;ii_lzeypikh0\u0026gt; X-Attachment-Id: ii_lzeypikh0 iVBORw0KGgoAAAANSUhEUgAAALAAAADbCAIAAABC5oyQAAAQQ0lEQVR4Ae2dz2sbRxvH339hiQkB 45IQ3CSu1IY0IAolhwRXlDqxsB3IQXUP8am02CRg8NU1tLm4PlSJ6KWlF1kWpoUUAkbVLj3pIogR 6CK37sEH51CffElA+KWe8DAzO # 以下略 ... 你可以注意到上方的內容有這段: Content-ID: \u0026lt;ii_lzeypikh0\u0026gt; ，這是這張圖片的在這個郵件內的唯一識別符，這個ID可以在 HTML 內容中使用 cid: 引用。\n因此，實現內嵌圖片的核心除了 multipart/related 以外，還有這段 HTML: \u0026lt;img src=\u0026quot;cid:ii_lzeypikh0\u0026quot; alt=\u0026quot;image.png\u0026quot; width=\u0026quot;176\u0026quot; height=\u0026quot;219\u0026quot;\u0026gt;\n其屬性如下： src=\u0026quot;cid:ii_lzeypikh0\u0026quot;：表示圖片來源是一個Content-ID，通常在郵件中用來內嵌圖片。 alt=\u0026quot;image.png\u0026quot;：替代文字，在圖片無法顯示時顯示此文字。 width=\u0026quot;176\u0026quot;：圖片的寬度為176像素。 height=\u0026quot;219\u0026quot;：圖片的高度為219像素。 你可能會感到疑惑，通常這裡比較常見就是放圖片 URL，例如 src=\u0026quot;https://my-image.com/image.png\u0026quot;\n然而，這裡的 src=\u0026quot;cid:ii_lzeypikh0\u0026quot; 是一種特殊的用法，用於電子郵件內嵌圖片。這種用法的 cid（Content-ID）表示圖片是電子郵件的一部分，而不是從外部連結加載。這樣的圖片內嵌方法通常用於確保圖片能夠在收件人的郵件客戶端中正確顯示，而不會受到外部圖片加載問題的影響。\n參考資料 RFC 2822: Internet Message Format 多用途網際網路郵件擴展 - 維基百科，自由的百科全書 ","permalink":"https://shiun.me/blog/troubleshooting-embedded-images-in-aws-ses-forwarded-emails-a-deep-dive-into-mime-and-content-ids/","summary":"我遇到了什麼問題 最近我用 SES, S3, Lambda 開發了轉寄信件的功能，簡單介紹一下這功能： 假設我就職於公司的開發部門，我們對外有一個信箱是 dev@example.com 當客戶遇到軟體開發上的問題時寄信到 dev@example.com，開發部門的所有同仁就會在自己的信箱收到此客戶寄來的電子郵件，重點是開發部門的同仁們都可以用自己的帳號回覆該客戶\n而開發好這功能後，我遇到一個問題 — 轉寄出去的信件沒辦法正確顯示內嵌圖片\n關於轉寄信件的程式碼我放在 GitHub 上囉 (連結)，之後有時間會寫一篇 Forward Email 的完整教學，或是也可以關注我的 Notion，我每天都會寫筆記記錄自己的學習過程\n讓我們直接看例子 首先，我登入私人帳號模擬我是客戶，寄信給 test@aws-educate.tw，下圖是當時寄出去的內容: 寄給 test@aws-educate.tw 後，SES 會收到信，然後根據我配置的 Receipt rule，原始郵件檔案會被儲存至我指定的 S3 Bucket\n延續 用 SES, S3, Lambda 實現轉寄信件功能 ，當我嘗試在轉寄出去的信件中內嵌圖片，最後收到轉寄來的信，都會出現下圖的現象：圖片沒辦法內嵌在裡面，倒是跑去附件了\n寫文章時，已經把原本轉寄過來的信件刪了，所以下圖信件主旨跟前一張圖片不一樣不要介意，下圖信件已經詮釋我想表達的現象了 XD\n我起初嘗試的解法 後來下面這篇 Stack Overflow，裡面有人提到 Gmail 還不支援顯示 base64 編碼的圖片 (有待確認，希望有人可以告訴我正確答案)\nAs of January 2020, Gmail still does not support base64 encoded images.\nSource: Add embedded image in emails in AWS SES service","title":"從 AWS SES 轉寄信件無法正確顯示內嵌圖片談起：MIME 內嵌圖片原理與實踐"},{"content":"程式碼在這邊的 feature/preload 分支: https://github.com/sh1un/Nextjs-Musive-app\n此專案是一個 Next.js 專案，原作者為 Ansh Rathod，我已經過作者本人授權使用，因此 Fork 過來稍微改了一下。\n我本身不是一名專精於前端技術的開發者，所以我所修改的 Code 都是由 ChatGPT-4 產生的。我今天這樣做的主要目的是想實驗 preload 是否能做到效能優化，以此來做一個簡單的 PoC。\n補充一下，這篇文章的內容本來是要放在 「2024 AWS Educate 陪跑計畫的獎勵課程 - 雲端串流挑戰：復刻 Spotify 的技術旅程」中教學，此工作坊旨在教學如何利用 CloudFront 優化速度與降低延遲，但礙於當天工作坊的內容太滿已經塞不下了，所以當天工作坊就沒有講到 Preload，如果看到這篇文章，想要學學 CloudFront 歡迎參觀我們當天的教材 (連結)\n什麼是 Preload? Preload 是一種網頁性能優化技術，讓瀏覽器在頁面加載時預先加載指定的資源，這樣在使用這些資源時可以更快地顯示或播放。這可以減少等待時間，提升用戶體驗。\n你可以想成：原本你是要“按下去播放按鈕”才會開始下載音樂，現在我們提前幫你下載，你之後“按下去播放按鈕”就會立即播放。\n舉例 如果你在網頁上有一段影片或音樂，你可以用 preload 告訴瀏覽器提前下載這些文件，這樣當用戶點擊播放按鈕時，影片或音樂就會立即播放，而不是先等待下載。\n基本用法： 在 HTML 中，你可以在 \u0026lt;link\u0026gt; 標籤中使用 rel=\u0026quot;preload\u0026quot; 來預加載資源。例如：\n\u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;path/to/your/file.mp3\u0026#34; as=\u0026#34;audio\u0026#34;\u0026gt; 這樣，瀏覽器會在頁面加載時預先下載 file.mp3，提高用戶播放音樂的速度。\n動態 Preload 的實現思路 每個用戶的歌單都不一樣，所以我們當然不希望把 preload 的 href 寫死成一個 URL。\n解決方案 我們可以通過以下幾個步驟來實現動態的 preload：\nAPI 獲取歌單： 伺服器端提供一個 API，根據用戶的請求返回該用戶的歌單。這個 API 返回一個 JSON 結構，其中包含了音樂檔案的 URL 列表。 JavaScript 動態生成 Preload 標籤： 使用 JavaScript 在頁面加載後動態地從 API 獲取歌單，並為每個音樂文件創建 preload 標籤，將其添加到頁面的 \u0026lt;head\u0026gt; 中。 具體實現 注意⚠️ 我的寫法並非添加 preload 到頁面的 \u0026lt;head\u0026gt;，而是直接用 React 和 JS 本身提供的內建物件來達到同樣效果，詳見本段說明。\n因為我們的 Ansh 寫的後端應用有提供這支“取得指定數量隨機音樂 API”，我會以這支 API 來取得隨機歌單：\nGET /api/songs/random/{songs_count}\nResponse (200)\n{ \u0026#34;success\u0026#34;: true, \u0026#34;data\u0026#34;: [ { \u0026#34;id\u0026#34;: 99123, \u0026#34;duration\u0026#34;: 197.877531, \u0026#34;track_name\u0026#34;: \u0026#34;Marching Music -The Crusader By John Philip Sousa\u0026#34;, \u0026#34;src\u0026#34;: \u0026#34;https://cdn.pixabay.com/audio/2022/03/21/audio_1a944368a1.mp3\u0026#34;, \u0026#34;cover_image\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;https://images.unsplash.com/photo-1482954363933-4bed6bbea570?ixid=MnwzODAxMTZ8MHwxfHNlYXJjaHwxMjd8fGx1eHVyeXxlbnwwfHx8fDE2NjgyNjIzOTI\u0026amp;ixlib=rb-4.0.3\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#0c2640\u0026#34; }, \u0026#34;artist_name\u0026#34;: \u0026#34;Jane Howe\u0026#34;, \u0026#34;artist_id\u0026#34;: 25232863 }, ... ] } 先來看一些核心的程式碼，最後會附上完整的 _app.tsx。\n這段程式碼在 MyApp 組件的 useEffect 中實現了動態 preload：\nuseEffect(() =\u0026gt; { // Fetch random songs and preload them const fetchAndPreloadSongs = async () =\u0026gt; { try { const { topHits } = await homePageApi.getRandomArtists(); preloadSongs(topHits); } catch (error) { console.error(\u0026#34;Error fetching random songs:\u0026#34;, error); } }; const preloadSongs = (songs: Song[]) =\u0026gt; { songs.forEach((song) =\u0026gt; { const audio = new Audio(); audio.src = song.src; audio.load(); audioElements.current.set(song.id, audio); console.log(`Preloading song: ${song.track_name}`); }); }; fetchAndPreloadSongs(); }, []); useEffect 這段程式碼在組件掛載 (mounted) 後執行，確保在頁面加載後立刻執行 preload 邏輯。 空的依賴陣列 [] 確保這段程式碼只在組件初始化時執行一次。 fetchAndPreloadSongs 該異步函數使用 homePageApi.getRandomArtists() 從 API 獲取隨機的音樂檔案。 獲取音樂後，調用 preloadSongs 函數進行 preload。 preloadSongs 該函數接收一個音樂列表，對每個音樂創建一個新的 Audio 對象。 設置 audio.src 為音樂的 URL，並調用 audio.load() 以便瀏覽器開始加載音樂文件。 將創建的 Audio 對象存儲在 audioElements 這個 Map 中，方便以後引用和控制。 _app.tsx 完整程式碼：\nimport \u0026#34;../styles/globals.css\u0026#34;; import type { AppProps } from \u0026#34;next/app\u0026#34;; import { Provider } from \u0026#34;react-redux\u0026#34;; import store from \u0026#34;../stores/store\u0026#34;; import NextNProgress from \u0026#34;nextjs-progressbar\u0026#34;; import { useRouter } from \u0026#34;next/router\u0026#34;; import AudioPlayer from \u0026#34;../components/AudioPlayer/AudioPlayer\u0026#34;; import SidebarItem from \u0026#34;../components/sidebarItem\u0026#34;; import Head from \u0026#34;next/head\u0026#34;; import \u0026#34;react-toastify/dist/ReactToastify.css\u0026#34;; import useDetectKeyboardOpen from \u0026#34;use-detect-keyboard-open\u0026#34;; import AddToCollectionModel from \u0026#34;@/components/AddToCollectionModel\u0026#34;; import { ToastContainer } from \u0026#34;react-toastify\u0026#34;; import { useEffect, useRef } from \u0026#34;react\u0026#34;; import homePageApi from \u0026#34;../stores/homePage/homePageApi\u0026#34;; import { Song } from \u0026#34;@/interfaces/Track\u0026#34;; function MyApp({ Component, pageProps }: AppProps) { const audioElements = useRef\u0026lt;Map\u0026lt;number, HTMLAudioElement\u0026gt;\u0026gt;(new Map()); useEffect(() =\u0026gt; { // Fetch random songs and preload them const fetchAndPreloadSongs = async () =\u0026gt; { try { const { topHits } = await homePageApi.getRandomArtists(); preloadSongs(topHits); } catch (error) { console.error(\u0026#34;Error fetching random songs:\u0026#34;, error); } }; const preloadSongs = (songs: Song[]) =\u0026gt; { songs.forEach((song) =\u0026gt; { const audio = new Audio(); audio.src = song.src; audio.load(); audioElements.current.set(song.id, audio); console.log(`Preloading song: ${song.track_name}`); }); }; fetchAndPreloadSongs(); }, []); return ( \u0026lt;Provider store={store}\u0026gt; \u0026lt;Head\u0026gt; \u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;/musive-icons.ttf\u0026#34; as=\u0026#34;font\u0026#34; crossOrigin=\u0026#34;\u0026#34; type=\u0026#34;font/ttf\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;/ProximaNova/Proxima Nova Reg.otf\u0026#34; as=\u0026#34;font\u0026#34; crossOrigin=\u0026#34;\u0026#34; type=\u0026#34;font/otf\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;/ProximaNova/Proxima Nova Bold.otf\u0026#34; as=\u0026#34;font\u0026#34; crossOrigin=\u0026#34;\u0026#34; type=\u0026#34;font/otf\u0026#34; /\u0026gt; \u0026lt;/Head\u0026gt; \u0026lt;NextNProgress color=\u0026#34;#2bb540\u0026#34; stopDelayMs={10} height={3} options={{ showSpinner: false }} /\u0026gt; \u0026lt;Component {...pageProps} /\u0026gt; \u0026lt;AudioPlayerComponent /\u0026gt; \u0026lt;/Provider\u0026gt; ); } function AudioPlayerComponent() { const router = useRouter(); const isKeyboardOpen = useDetectKeyboardOpen(); return ( \u0026lt;div\u0026gt; \u0026lt;ToastContainer position=\u0026#34;top-center\u0026#34; autoClose={1000} hideProgressBar newestOnTop={ false} closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover theme=\u0026#34;dark\u0026#34; /\u0026gt; \u0026lt;AddToCollectionModel /\u0026gt; {router.pathname !== \u0026#34;/login\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/register\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/_error\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/\u0026#34; ? ( \u0026lt;AudioPlayer className={isKeyboardOpen ? \u0026#34;invisible\u0026#34; : \u0026#34;visible\u0026#34;} /\u0026gt; ) : ( \u0026lt;div\u0026gt;\u0026lt;/div\u0026gt; )} {router.pathname !== \u0026#34;/login\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/register\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/_error\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/playing\u0026#34; \u0026amp;\u0026amp; router.pathname !== \u0026#34;/\u0026#34; \u0026amp;\u0026amp; ( \u0026lt;div className={`bg-[#121212] hidden mobile:block tablet:block fixed bottom-0 left-0 right-0 w-full pt-2 pb-1 z-20 ${ isKeyboardOpen ? \u0026#34;invisible\u0026#34; : \u0026#34;visible\u0026#34; }`} \u0026gt; \u0026lt;div className=\u0026#34;flex flex-row justify-center \u0026#34;\u0026gt; \u0026lt;SidebarItem name=\u0026#34;home\u0026#34; label=\u0026#34;Home\u0026#34; /\u0026gt; \u0026lt;SidebarItem name=\u0026#34;search\u0026#34; label=\u0026#34;Search\u0026#34; /\u0026gt; \u0026lt;SidebarItem name=\u0026#34;library\u0026#34; label=\u0026#34;Library\u0026#34; /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; )} \u0026lt;/div\u0026gt; ); } export default MyApp; 使用 Preload 前 vs 使用後 使用前 請注意，第二首是按下播放按鈕後才開始下載音樂：\n使用後 補充 - 在舊有的機器改成 Preload 這邊內容僅適用於有照著工作坊內容實作的同學，以下步驟將教學你如何把原有的 Application 套用 Dynamic Preload：\n連線上去 EC2 cd Nextjs-Musive-app git checkout feature/preload TOKEN=$(curl -X PUT \u0026quot;http://169.254.169.254/latest/api/token\u0026quot; -H \u0026quot;X-aws-ec2-metadata-token-ttl-seconds: 21600\u0026quot;) export MUSIVE_API_URL=\u0026quot;http://$(curl -H \u0026quot;X-aws-ec2-metadata-token: $TOKEN\u0026quot; http://169.254.169.254/latest/meta-data/public-ipv4):4444/api\u0026quot; npm install npm run build pm2 restart nextjs-app Resources Notion - 20240705 獎勵課程 - 雲端串流挑戰：復刻 Spotify 的技術旅程 GitHub - Ansh-Rathod / Nextjs-Musive-app ","permalink":"https://shiun.me/blog/dynamic-preload-for-performance-optimization/","summary":"程式碼在這邊的 feature/preload 分支: https://github.com/sh1un/Nextjs-Musive-app\n此專案是一個 Next.js 專案，原作者為 Ansh Rathod，我已經過作者本人授權使用，因此 Fork 過來稍微改了一下。\n我本身不是一名專精於前端技術的開發者，所以我所修改的 Code 都是由 ChatGPT-4 產生的。我今天這樣做的主要目的是想實驗 preload 是否能做到效能優化，以此來做一個簡單的 PoC。\n補充一下，這篇文章的內容本來是要放在 「2024 AWS Educate 陪跑計畫的獎勵課程 - 雲端串流挑戰：復刻 Spotify 的技術旅程」中教學，此工作坊旨在教學如何利用 CloudFront 優化速度與降低延遲，但礙於當天工作坊的內容太滿已經塞不下了，所以當天工作坊就沒有講到 Preload，如果看到這篇文章，想要學學 CloudFront 歡迎參觀我們當天的教材 (連結)\n什麼是 Preload? Preload 是一種網頁性能優化技術，讓瀏覽器在頁面加載時預先加載指定的資源，這樣在使用這些資源時可以更快地顯示或播放。這可以減少等待時間，提升用戶體驗。\n你可以想成：原本你是要“按下去播放按鈕”才會開始下載音樂，現在我們提前幫你下載，你之後“按下去播放按鈕”就會立即播放。\n舉例 如果你在網頁上有一段影片或音樂，你可以用 preload 告訴瀏覽器提前下載這些文件，這樣當用戶點擊播放按鈕時，影片或音樂就會立即播放，而不是先等待下載。\n基本用法： 在 HTML 中，你可以在 \u0026lt;link\u0026gt; 標籤中使用 rel=\u0026quot;preload\u0026quot; 來預加載資源。例如：\n\u0026lt;link rel=\u0026#34;preload\u0026#34; href=\u0026#34;path/to/your/file.mp3\u0026#34; as=\u0026#34;audio\u0026#34;\u0026gt; 這樣，瀏覽器會在頁面加載時預先下載 file.mp3，提高用戶播放音樂的速度。\n動態 Preload 的實現思路 每個用戶的歌單都不一樣，所以我們當然不希望把 preload 的 href 寫死成一個 URL。\n解決方案 我們可以通過以下幾個步驟來實現動態的 preload：\nAPI 獲取歌單： 伺服器端提供一個 API，根據用戶的請求返回該用戶的歌單。這個 API 返回一個 JSON 結構，其中包含了音樂檔案的 URL 列表。 JavaScript 動態生成 Preload 標籤： 使用 JavaScript 在頁面加載後動態地從 API 獲取歌單，並為每個音樂文件創建 preload 標籤，將其添加到頁面的 \u0026lt;head\u0026gt; 中。 具體實現 注意⚠️ 我的寫法並非添加 preload 到頁面的 \u0026lt;head\u0026gt;，而是直接用 React 和 JS 本身提供的內建物件來達到同樣效果，詳見本段說明。","title":"在 Next.js 音樂應用中使用動態 Preload 技術提升前端效能降低延遲"},{"content":"大家好，我是 Shiun，今天想和大家分享我在 eCloudValley MSP 部門擔任雲端工程師實習生的經歷。這段實習讓我學到了很多實用的雲端技術，同時也在工作中遇到了許多有趣的挑戰。 實習工作內容 在 eCloudValley 的實習期間，我主要負責以下幾項工作：\n學習使用公有雲，包括 AWS 和 Azure。 開發公司內部的 GenAI 應用。 與同期且同部門實習生共同開發專案。 定期與 Mentor 開會。 參與最終展演。 撰寫系統文件。 實習期間大事紀 專案大改方向 在 eCloudValley 實習期間，我們的專案經歷了一次重大轉變。起初，我們的任務是開發一個 AI Bot 開單助理，目的是幫助客戶在雲端遇到問題時能夠迅速獲得支援。這個專案的初步構想是：\n客戶向 AI 助理求助，並向 AI 助理提供必要資訊，例如： EC2 SG Inbound Rule, 健康檢查狀態\u0026hellip; 等等 如果客戶提供的資訊不夠明確，AI 助理要明確地告知客戶還需提供哪些資訊。 AI 助理彙總客戶問題，轉化為技術人員易於理解的語言。 將資訊轉化後，開 ticket 到開單系統。 然而，在實作過程中，我們發現這個 AI 助理的效果不如預期，且後續優化存在技術瓶頸。客戶問題過於多樣化、且問題幾乎都很具情境，不是那種有標準解答或是標準 SOP 就能解決的問題，所以 AI 助理難以處理，導致準確率低，最終還是需要人工介入，反而增加了工作量。\n因此，我們在 4 月底決定將專案轉變為一個「會議錄影機器人」。這個機器人會被邀請到 LINE 群組中，默默記錄每一條訊息以及圖片 (圖片透過 Claude 3 Sonnet 取得 Caption)。TAM 只需在群組中輸入特殊指令或貼圖即可控制錄影，最終在前端頁面查看記錄並由 LLM 總結，創建 Ticket 到 MSP 部門的 Ticket System。\n獲得 Best Tech Team 專案在 4月底、5月初大改方向，而我們必須在 5 月底展演。儘管時程緊湊，但團隊合作逐漸流暢，我們在短短不到一個月內完成了應用開發。團隊的 PO (Product Owner) 帶領我們不斷練習簡報，最終在 5/31 的展演中獲得了 Best Tech Team 的獎項，這讓我們非常開心。 實習中的專業學習心得 學會使用 AWS 及 Azure 在公有雲市場中，AWS 和 Azure 占據了領先地位。在 eCloudValley，我們通過內部的知識管理系統 (eCloudTure) 學習如何使用這些平台來部署和架設服務。這個平台讓我們可以依照課程實作 Lab，不僅學會了理論知識，還學會了動手操作。\n學習使用 GenAI 技術 在實習期間，我們需要開發一個 GenAI 應用，幫助 TAM 自動總結客戶問題並開工單給工程師解決。我學會了使用 LangChain 框架和 Azure OpenAI, Prompt Flow，但最終選擇了 AWS Bedrock 的 Claude 3 Sonnet 作為模型。學習這些技術讓我在 GenAI Hackathon 中得到了實踐機會，也讓我在使用 AI 工具時更加得心應手。\n加強了英文口說和閱讀 由於 eCloudValley 跨足多國，內部必須使用英文交流。我們每天都要進行英文開會 (Daily Scrum)，並在最終展演時使用全英文簡報。這段經歷強迫我提升英文口說能力，讓我在短短三個月內英文口說更為流暢。\n關於我喜歡 eCloudValley 的點 文化及地理面 年輕的氛圍：MSP 部門氣氛歡樂，而且主管們都很照顧。 DevOps 主管 Timmy 重視員工想法：會依照我們的專長和想要發展的領域來安排任務，非常重視員工想法。 優秀的 Mentor：特別是 KKueen！真的很有耐心。 遇到這麼棒的 Mentor 真的很有福氣\n豐富的社團活動：公司有各種社團，咖啡社特別有趣。 便利的地理位置：公司離捷運站超近，從三和國中站出來就是公司，超級方便。 食物面 喝不完的飲品：咖啡、牛奶和豆漿應有盡有。 免費午餐：中午提供免費午餐，偶爾還有西瓜，超級爽，我都吃超多西瓜。 冰棒和零食：各種零食和冰棒供應，隨時可以享用。 啤酒和調酒：雖然很少喝，但在專案尾聲放鬆一下非常不錯。 學習面 雲端平台練習：公司提供 AWS 和 Azure 帳號，讓實習生可以練習雲端技術和進行 PoC。 公司教育系統 - eCloudTure：提供 Lab 環境和完整的教材，讓我們充分練習 AWS 和 Azure。 技術講座：作為 AWS Partner，公司不定期會有原廠技術講座，對於雲端技術愛好者來說非常棒。 實習之反省與檢討 英文的重要性 在這段實習中，我深刻體會到英文的重要性。無論是學習資源還是日常工作，英文都是不可或缺的工具。掌握好英文，才能在軟體開發這條路上如魚得水。\n紀錄自己的成長 實習期間，我們的 Mentor 要求我們寫日誌，紀錄每天的學習。這讓我意識到筆記不僅是為了複習，更是為了意識到自己的成長。通過不斷的紀錄和反思，我感受到成就感，進一步提升了學習動力。\n已經養成一個習慣了 XD，直至今日每天都在寫\n結語 在 eCloudValley 的實習經歷，是我職涯中的一個重要里程碑。不僅提升了技術能力，也讓我在團隊合作和問題解決上得到了寶貴的經驗。這段實習讓我更具信心，迎接未來的挑戰。\n希望我的分享對大家有所幫助，也希望有更多的機會能和大家交流技術心得。感謝大家的閱讀！\n","permalink":"https://shiun.me/blog/2024-ecloudvalley-internship/","summary":"大家好，我是 Shiun，今天想和大家分享我在 eCloudValley MSP 部門擔任雲端工程師實習生的經歷。這段實習讓我學到了很多實用的雲端技術，同時也在工作中遇到了許多有趣的挑戰。 實習工作內容 在 eCloudValley 的實習期間，我主要負責以下幾項工作：\n學習使用公有雲，包括 AWS 和 Azure。 開發公司內部的 GenAI 應用。 與同期且同部門實習生共同開發專案。 定期與 Mentor 開會。 參與最終展演。 撰寫系統文件。 實習期間大事紀 專案大改方向 在 eCloudValley 實習期間，我們的專案經歷了一次重大轉變。起初，我們的任務是開發一個 AI Bot 開單助理，目的是幫助客戶在雲端遇到問題時能夠迅速獲得支援。這個專案的初步構想是：\n客戶向 AI 助理求助，並向 AI 助理提供必要資訊，例如： EC2 SG Inbound Rule, 健康檢查狀態\u0026hellip; 等等 如果客戶提供的資訊不夠明確，AI 助理要明確地告知客戶還需提供哪些資訊。 AI 助理彙總客戶問題，轉化為技術人員易於理解的語言。 將資訊轉化後，開 ticket 到開單系統。 然而，在實作過程中，我們發現這個 AI 助理的效果不如預期，且後續優化存在技術瓶頸。客戶問題過於多樣化、且問題幾乎都很具情境，不是那種有標準解答或是標準 SOP 就能解決的問題，所以 AI 助理難以處理，導致準確率低，最終還是需要人工介入，反而增加了工作量。\n因此，我們在 4 月底決定將專案轉變為一個「會議錄影機器人」。這個機器人會被邀請到 LINE 群組中，默默記錄每一條訊息以及圖片 (圖片透過 Claude 3 Sonnet 取得 Caption)。TAM 只需在群組中輸入特殊指令或貼圖即可控制錄影，最終在前端頁面查看記錄並由 LLM 總結，創建 Ticket 到 MSP 部門的 Ticket System。","title":"2024 伊雲谷實習計畫 - 雲端工程師實習心得"},{"content":"情境描述 上圖顯示的是我剛在 Cognito User Pool 中新增了一個 user，可以注意到箭頭處顯示 Force change password。\n當這個 user 嘗試首次登入時，他們會遇到這樣的情況:\n{ \u0026#34;message\u0026#34;: \u0026#34;New password required\u0026#34;， \u0026#34;challengeName\u0026#34;: \u0026#34;NEW_PASSWORD_REQUIRED\u0026#34;， \u0026#34;session\u0026#34;: \u0026#34;AYABeAsYJsR3yEr0iJssKPPUPEgAHQABAAdTZXJ2aWNlABBDb2duaXRvVXNlclBvb2xzAAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjowMTU3MzY3MjcxOTg6a2V5LzI5OTFhNGE5LTM5YTAtNDQ0Mi04MWU4LWRkYjY4NTllMTg2MQC4AQIBAHhPj7k9zU4nGXUQUvM0Ccwk42DS-fm3vKmH75ktTrktNQG1gnjl6HkUVUYN1J_HPow6AAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMulTha32s34j2CmQWAgEQgDtog8CDFh2e-4YyjM2kB_MXheMgmrdY_IF3aN9TImZXddMBj7djEAPPduLZnG3ddBLYQa8x3T3WPKUkvwIAAAAADAAAEAAAAAAAAAAAAAAAAADCbdmpLPo0E4QkWLlyH8ov_____wAAAAEAAAAAAAAAAAAAAAEAAAC1oMtgshmuUU4fk36WHKBzPgJEoE1MmL0PFyhR9lRcimImOIObhhxvC1fwiYylgbYx0Gu0i1cp5Le8AvrAnUGEJjZp54TMPP4N-JCT3qSrHeq_Kat_2CuECVSQqkc1qH4z9FVOTvAnos4FrDSn2W6KvFfLo8YQh2LJxM1h3GdIeyYqj7Ipfk6PZKGYmV5P741rRMNcuYBtvE8Hq9gVqMbEPG-c5MppY_q9JoG9TyQRN7rVGlZf62_WtTqST2F3-DZPoXTMTyY\u0026#34;， \u0026#34;challengeParameters\u0026#34;: { \u0026#34;USER_ID_FOR_SRP\u0026#34;: \u0026#34;shiun\u0026#34;， \u0026#34;requiredAttributes\u0026#34;: \u0026#34;[]\u0026#34;， \u0026#34;userAttributes\u0026#34;: \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;xxxxx@gmail.com\\\u0026#34;}\u0026#34; } } Cognito 回傳了一個 NEW_PASSWORD_REQUIRED 的 challenge，同時給了我們一個 session token。這就是我們需要處理的 case。\n解決方案 要解決這個問題，我們需要提供一個 change-password 的 API endpoint，讓 user 可以順利更改密碼。整個 flow 大致如下:\nUser 首次登入 Cognito 回傳 NEW_PASSWORD_REQUIRED challenge 和 session token 前端帶著 session token 呼叫我們的 change-password API 密碼更改成功，user 順利登入系統 以下是一個處理這種情況的 Lambda function 範例:\nimport json import logging import boto3 from botocore.exceptions import ClientError # Initialize logger logger = logging.getLogger() logger.setLevel(logging.INFO) # Initialize Cognito client client = boto3.client(\u0026#34;cognito-idp\u0026#34;) def lambda_handler(event, context): \u0026#34;\u0026#34;\u0026#34; Handles the NEW_PASSWORD_REQUIRED challenge from Cognito \u0026#34;\u0026#34;\u0026#34; try: # Parse the request body body = json.loads(event[\u0026#34;body\u0026#34;]) # Respond to the new password required challenge response = client.respond_to_auth_challenge( ClientId=\u0026#34;YOUR_CLIENT_ID\u0026#34;, ChallengeName=\u0026#34;NEW_PASSWORD_REQUIRED\u0026#34;, Session=body[\u0026#34;session\u0026#34;], ChallengeResponses={ \u0026#34;USERNAME\u0026#34;: body[\u0026#34;username\u0026#34;], \u0026#34;NEW_PASSWORD\u0026#34;: body[\u0026#34;new_password\u0026#34;], # Add any required attributes here if necessary }, ) logger.info(\u0026#34;Cognito respond to challenge response: %s\u0026#34;, response) # Extract access token from the response access_token = response[\u0026#34;AuthenticationResult\u0026#34;][\u0026#34;AccessToken\u0026#34;] # Return successful response with the access token set in cookies return { \u0026#34;statusCode\u0026#34;: 200, \u0026#34;headers\u0026#34;: { \u0026#34;Set-Cookie\u0026#34;: f\u0026#34;accessToken={access_token}; Path=/; Secure; HttpOnly; SameSite=None; Domain=.aws-educate.tw\u0026#34; }, \u0026#34;body\u0026#34;: json.dumps({\u0026#34;message\u0026#34;: \u0026#34;Password changed successfully\u0026#34;}), } except ClientError as e: # Handle Cognito client errors logger.error(\u0026#34;Cognito client error: %s\u0026#34;, e) return { \u0026#34;statusCode\u0026#34;: 400, \u0026#34;body\u0026#34;: json.dumps({\u0026#34;message\u0026#34;: e.response[\u0026#34;Error\u0026#34;][\u0026#34;Message\u0026#34;]}), } except json.JSONDecodeError as e: # Handle JSON decoding errors logger.error(\u0026#34;JSON decode error: %s\u0026#34;, e) return { \u0026#34;statusCode\u0026#34;: 400, \u0026#34;body\u0026#34;: json.dumps({\u0026#34;message\u0026#34;: \u0026#34;Invalid JSON format in request body\u0026#34;}), } 這個 Lambda function 做了以下幾件事:\n接收包含 username、new password 和 session token 的 request 使用 Cognito 的 respond_to_auth_challenge API 來處理 NEW_PASSWORD_REQUIRED challenge 如果密碼更改成功，從 response 中提取 access token 將 access token 設置在 cookie 中，並返回成功訊息 透過這樣的處理，我們可以讓新用戶順利完成首次登入時的密碼更改流程，提供更好的 user experience。\n記得，在實際部署時，要根據你的具體需求來調整這個 function，比如錯誤處理、日誌記錄等。同時，也要確保前端能夠正確處理這個流程，在收到 NEW_PASSWORD_REQUIRED challenge 時，引導用戶到密碼更改的頁面。\n希望這個範例能幫助讀者更好地處理 AWS Cognito 的首次登入密碼更改流程\n","permalink":"https://shiun.me/blog/aws-cognito-how-to-handle-force-change-password/","summary":"情境描述 上圖顯示的是我剛在 Cognito User Pool 中新增了一個 user，可以注意到箭頭處顯示 Force change password。\n當這個 user 嘗試首次登入時，他們會遇到這樣的情況:\n{ \u0026#34;message\u0026#34;: \u0026#34;New password required\u0026#34;， \u0026#34;challengeName\u0026#34;: \u0026#34;NEW_PASSWORD_REQUIRED\u0026#34;， \u0026#34;session\u0026#34;: \u0026#34;AYABeAsYJsR3yEr0iJssKPPUPEgAHQABAAdTZXJ2aWNlABBDb2duaXRvVXNlclBvb2xzAAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjowMTU3MzY3MjcxOTg6a2V5LzI5OTFhNGE5LTM5YTAtNDQ0Mi04MWU4LWRkYjY4NTllMTg2MQC4AQIBAHhPj7k9zU4nGXUQUvM0Ccwk42DS-fm3vKmH75ktTrktNQG1gnjl6HkUVUYN1J_HPow6AAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMulTha32s34j2CmQWAgEQgDtog8CDFh2e-4YyjM2kB_MXheMgmrdY_IF3aN9TImZXddMBj7djEAPPduLZnG3ddBLYQa8x3T3WPKUkvwIAAAAADAAAEAAAAAAAAAAAAAAAAADCbdmpLPo0E4QkWLlyH8ov_____wAAAAEAAAAAAAAAAAAAAAEAAAC1oMtgshmuUU4fk36WHKBzPgJEoE1MmL0PFyhR9lRcimImOIObhhxvC1fwiYylgbYx0Gu0i1cp5Le8AvrAnUGEJjZp54TMPP4N-JCT3qSrHeq_Kat_2CuECVSQqkc1qH4z9FVOTvAnos4FrDSn2W6KvFfLo8YQh2LJxM1h3GdIeyYqj7Ipfk6PZKGYmV5P741rRMNcuYBtvE8Hq9gVqMbEPG-c5MppY_q9JoG9TyQRN7rVGlZf62_WtTqST2F3-DZPoXTMTyY\u0026#34;， \u0026#34;challengeParameters\u0026#34;: { \u0026#34;USER_ID_FOR_SRP\u0026#34;: \u0026#34;shiun\u0026#34;， \u0026#34;requiredAttributes\u0026#34;: \u0026#34;[]\u0026#34;， \u0026#34;userAttributes\u0026#34;: \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;xxxxx@gmail.com\\\u0026#34;}\u0026#34; } } Cognito 回傳了一個 NEW_PASSWORD_REQUIRED 的 challenge，同時給了我們一個 session token。這就是我們需要處理的 case。\n解決方案 要解決這個問題，我們需要提供一個 change-password 的 API endpoint，讓 user 可以順利更改密碼。整個 flow 大致如下:\nUser 首次登入 Cognito 回傳 NEW_PASSWORD_REQUIRED challenge 和 session token 前端帶著 session token 呼叫我們的 change-password API 密碼更改成功，user 順利登入系統 以下是一個處理這種情況的 Lambda function 範例:","title":"如何使用 Lambda 處理 AWS Cognito User 首次登入被系統要求強制變更密碼"},{"content":"在 AWS API Gateway 創建 method 時，我們可以指定他要把請求傳到哪個 AWS 服務或是其他 HTTP Endpoint\u0026hellip;等等，而如果你選擇 Lambda，你會看到有一個選項是 Lambda Proxy Integration，那我把它勾選起來會怎樣呢？\nTL;DR 如果有打開 Lambda Proxy Integration，接著我們到那個 Resource 的 Method 頁面看一下，會看到下圖的提示 Proxy integration For proxy integrations, API Gateway automatically passes the backend output to the caller as the complete payload.\n但也許沒有很好理解，所以我們後面會實際打 Code 來示範，但我這邊先稍微解釋一下打開這個功能的影響:\n完整的 HTTP 請求資訊：啟用 Lambda Proxy Integration 後，API Gateway 會將整個 HTTP 請求打包成單一的 event object，並傳遞給 Lambda Function 簡化的處理流程：Lambda Function 接收到的是一個包含了所有請求資訊的 event object，因此不需要再通過模板或轉換就可以直接處理請求。這樣簡化了函數的輸入處理，因為 Lambda Function 已經包含了所有必要的請求資訊 控制 HTTP Response：Lambda Function 需要返回一個特定格式的物件，這個物件包括 Status Code、Response Headers 和 Response Body。由於 Response 是直接由 Lambda Function 生成的，所以開發者可以完全控制 Response 的格式和內容 效能和成本效益：由於請求處理的過程更加直接，減少了中間的處理和轉換步驟，可以提高處理效率，可能也會在某種程度上降低成本 總之，選擇 Lambda Proxy Integration 可以使得與 AWS Lambda 的整合更加緊密、直接和高效。這適合需要精細控制 HTTP 響應的場景，或者希望簡化 API 與 Lambda 之間交互的架構設計\n來看實際的例子先來感受有無開啟的行為差異 我現在創建了兩個 API Gateway -\u0026gt; Lambda 的組合:\n使用 Proxy Integration 不使用 Proxy Integration 而程式碼目前都是:\nimport json def lambda_handler(event, context): # TODO implement return { \u0026#39;statusCode\u0026#39;: 200, \u0026#39;body\u0026#39;: json.dumps(\u0026#39;Hello from Lambda!\u0026#39;) } 接著我們分別調用看看這兩支 API\nWith Lambda Proxy Integration # With Lambda Proxy Integration $ curl -X GET \u0026#34;https://m13nrn7ks1.execute-api.us-west-2.amazonaws.com/dev\u0026#34; \u0026#34;Hello from Lambda!\u0026#34; Non Lambda Proxy Integration # Non Lambda Proxy Integration $ curl -X GET https://byjlq9pv48.execute-api.us-west-2.amazonaws.com/dev/ {\u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;\\\u0026#34;Hello from Lambda!\\\u0026#34;\u0026#34;} 可以注意到，回傳的格式不太一樣，前者是回傳單純的字串，後者才是包在 Json 物件內而且還多了 statusCode 在 Response Body，具體原因後面會解釋\n修改 Lambda Function 內 Return 的 Status Code 我們緊接著來改一下 Lambda Function 的程式碼，我把 statusCode 改成 400，所以現在的完整程式碼如下:\nimport json def lambda_handler(event, context): # TODO implement return { \u0026#39;statusCode\u0026#39;: 400, # 修改這行 \u0026#39;body\u0026#39;: json.dumps(\u0026#39;Hello from Lambda!\u0026#39;) } 一樣我依序調用 API，但是我這邊改成用 Postman 去調用，這樣比較好閱讀和觀覽，請幫我注意下面兩張圖片的紅框處:\nWith Lambda Proxy Integration Non Lambda Proxy Integration 注意到了嗎？有沒有打開 Lambda Proxy Integration 其行為差異會如此大，若有開啟 Lambda Proxy Integration，那他會直接把你在 Function 內設定的 statusCode 映射到 Response 的 Status Code；而沒有開啟的話，他會把你的 statusCode 包在 Response Body 內，這樣的行為是不是很不直覺呢？\n加入 Query Parameters 接著我現在要在 API 調用時，加上 Query Parameter name=Shiun，並且我也修改了程式碼:\nimport json def lambda_handler(event, context): name = event[\u0026#34;queryStringParameters\u0026#34;][\u0026#34;name\u0026#34;] # 從 evnet object 取得 Query Parameter return { \u0026#39;statusCode\u0026#39;: 200, # 改回 200 \u0026#39;body\u0026#39;: json.dumps(f\u0026#39;Hello {name}!\u0026#39;) } 一樣我再次分別調用兩支 API\nWith Lambda Proxy Integration Non Lambda Proxy Integration 可以注意到，有使用 Lambda Proxy Integration 的正確接到值了；而另一個沒有開啟的則出現錯誤，錯誤很明顯就是他找不到對應的 Key\n加入自定義 Header 接下來我要在Lambda Function 中 return 自定義的 Header: X-Shiun-Custom-Header\u0026quot; : \u0026quot;test\u0026quot;\n也是一樣，我會分別調用這兩支 API，我們來觀察 Response Headers\nWith Lambda Proxy Integration Non Lambda Proxy Integration 觀察到兩者的差異了嗎？有啟用 Lambda Proxy Integration 的那支 API 會看到 Response Headers 裡面真的有回傳 X-Shiun-Custom-Header\u0026quot; : \u0026quot;test\u0026quot;\nLambda Proxy Integration 的工作原理 到底是什麼原因會出現這樣的行為差異呢？所以我們來看看 Lambda Proxy Integration 的工作原理\n啟用 Lambda Proxy Integration 後，API Gateway 會將整個 HTTP 請求(包括request headers, query string parameters, URL path variables, payload 和 API configuration data) 打包成單一的 event object，並傳遞給 Lambda 函數。這也解釋為什麼我 call API 如果帶了 Query Param ，我使用這種寫法 name = event[\u0026quot;queryStringParameters\u0026quot;][\u0026quot;name\u0026quot;] 能抓到 API 請求的 Query Parameter\n而這個 event object 實際長這樣:\n{ \u0026#34;resource\u0026#34;: \u0026#34;/my/path\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/my/path\u0026#34;, \u0026#34;httpMethod\u0026#34;: \u0026#34;GET\u0026#34;, \u0026#34;headers\u0026#34;: { \u0026#34;header1\u0026#34;: \u0026#34;value1\u0026#34;, \u0026#34;header2\u0026#34;: \u0026#34;value1,value2\u0026#34; }, \u0026#34;multiValueHeaders\u0026#34;: { \u0026#34;header1\u0026#34;: [ \u0026#34;value1\u0026#34; ], \u0026#34;header2\u0026#34;: [ \u0026#34;value1\u0026#34;, \u0026#34;value2\u0026#34; ] }, \u0026#34;queryStringParameters\u0026#34;: { \u0026#34;parameter1\u0026#34;: \u0026#34;value1,value2\u0026#34;, \u0026#34;parameter2\u0026#34;: \u0026#34;value\u0026#34; }, \u0026#34;multiValueQueryStringParameters\u0026#34;: { \u0026#34;parameter1\u0026#34;: [ \u0026#34;value1\u0026#34;, \u0026#34;value2\u0026#34; ], \u0026#34;parameter2\u0026#34;: [ \u0026#34;value\u0026#34; ] }, \u0026#34;requestContext\u0026#34;: { \u0026#34;accountId\u0026#34;: \u0026#34;123456789012\u0026#34;, \u0026#34;apiId\u0026#34;: \u0026#34;id\u0026#34;, \u0026#34;authorizer\u0026#34;: { \u0026#34;claims\u0026#34;: null, \u0026#34;scopes\u0026#34;: null }, \u0026#34;domainName\u0026#34;: \u0026#34;id.execute-api.us-east-1.amazonaws.com\u0026#34;, \u0026#34;domainPrefix\u0026#34;: \u0026#34;id\u0026#34;, \u0026#34;extendedRequestId\u0026#34;: \u0026#34;request-id\u0026#34;, \u0026#34;httpMethod\u0026#34;: \u0026#34;GET\u0026#34;, \u0026#34;identity\u0026#34;: { \u0026#34;accessKey\u0026#34;: null, \u0026#34;accountId\u0026#34;: null, \u0026#34;caller\u0026#34;: null, \u0026#34;cognitoAuthenticationProvider\u0026#34;: null, \u0026#34;cognitoAuthenticationType\u0026#34;: null, \u0026#34;cognitoIdentityId\u0026#34;: null, \u0026#34;cognitoIdentityPoolId\u0026#34;: null, \u0026#34;principalOrgId\u0026#34;: null, \u0026#34;sourceIp\u0026#34;: \u0026#34;IP\u0026#34;, \u0026#34;user\u0026#34;: null, \u0026#34;userAgent\u0026#34;: \u0026#34;user-agent\u0026#34;, \u0026#34;userArn\u0026#34;: null, \u0026#34;clientCert\u0026#34;: { \u0026#34;clientCertPem\u0026#34;: \u0026#34;CERT_CONTENT\u0026#34;, \u0026#34;subjectDN\u0026#34;: \u0026#34;www.example.com\u0026#34;, \u0026#34;issuerDN\u0026#34;: \u0026#34;Example issuer\u0026#34;, \u0026#34;serialNumber\u0026#34;: \u0026#34;a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1\u0026#34;, \u0026#34;validity\u0026#34;: { \u0026#34;notBefore\u0026#34;: \u0026#34;May 28 12:30:02 2019 GMT\u0026#34;, \u0026#34;notAfter\u0026#34;: \u0026#34;Aug 5 09:36:04 2021 GMT\u0026#34; } } }, \u0026#34;path\u0026#34;: \u0026#34;/my/path\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;HTTP/1.1\u0026#34;, \u0026#34;requestId\u0026#34;: \u0026#34;id=\u0026#34;, \u0026#34;requestTime\u0026#34;: \u0026#34;04/Mar/2020:19:15:17 +0000\u0026#34;, \u0026#34;requestTimeEpoch\u0026#34;: 1583349317135, \u0026#34;resourceId\u0026#34;: null, \u0026#34;resourcePath\u0026#34;: \u0026#34;/my/path\u0026#34;, \u0026#34;stage\u0026#34;: \u0026#34;$default\u0026#34; }, \u0026#34;pathParameters\u0026#34;: null, \u0026#34;stageVariables\u0026#34;: null, \u0026#34;body\u0026#34;: \u0026#34;Hello from Lambda!\u0026#34;, \u0026#34;isBase64Encoded\u0026#34;: false } 此外，啟用 Lambda Proxy Integration，你也必須遵守特定的 Output Format，你才能正確地把 Response 傳遞出去:\n{ \u0026#34;isBase64Encoded\u0026#34;: true|false, \u0026#34;statusCode\u0026#34;: httpStatusCode, \u0026#34;headers\u0026#34;: { \u0026#34;headerName\u0026#34;: \u0026#34;headerValue\u0026#34;, ... }, \u0026#34;multiValueHeaders\u0026#34;: { \u0026#34;headerName\u0026#34;: [\u0026#34;headerValue\u0026#34;, \u0026#34;headerValue2\u0026#34;, ...], ... }, \u0026#34;body\u0026#34;: \u0026#34;...\u0026#34; } 所以這邊也解釋了，為什麼設定好 statusCode、headers，我就能控制整個 Response 回應的 Status Code 和 Headers，這中間都是因為有了 Lambda Proxy Integration 幫我們簡化了這中間繁雜的配置，如果沒有 Lambda Proxy Integration，那我們就必須手動去 API Gateway 那邊設定 Mapping Template，配置起來相當麻煩。\n那我到底要不要打開 Lambda Proxy Integration? 我會高度推薦打開，因為你可以在程式碼那邊控制 Response，這我不認為不只是「簡化」，而是「大大降低管理複雜度」，我認為這是一個更佳的管理方式 (這邊是我的主觀感受，並不代表完全正確)。\n甚至有時候我都會直接果斷啟用 Lambda Proxy Integration，然後在 API Gateway 後面加一層 Lambda 專門做參數驗證再轉發到實際後端的 Lambda Function，而非選擇在 API Gateway 那邊做請求預處理或是參數驗證。但實際仍需要以本身的業務需求去挑出最佳的解決方案，這邊只是提供一個我認為對開發人員很友善的處理方式，而且這種方式你也可以在程式碼內做出高度客製化\n總而言之，如果你沒有使用 Lambda Proxy Integration，則需要在 API Gateway 中配置額外的轉換模板來處理輸入和輸出格式。\n希望這篇文章有幫助你更加了解 Lambda Proxy Integration 的功能和行為，並且能夠在實際應用中更好地使用這個功能！\nResources Using AWS API Gateway as Proxy to other HTTP Endpoints Set up Lambda proxy integrations in API Gateway - Amazon API Gateway ","permalink":"https://shiun.me/blog/understanding-lambda-proxy-integration/","summary":"在 AWS API Gateway 創建 method 時，我們可以指定他要把請求傳到哪個 AWS 服務或是其他 HTTP Endpoint\u0026hellip;等等，而如果你選擇 Lambda，你會看到有一個選項是 Lambda Proxy Integration，那我把它勾選起來會怎樣呢？\nTL;DR 如果有打開 Lambda Proxy Integration，接著我們到那個 Resource 的 Method 頁面看一下，會看到下圖的提示 Proxy integration For proxy integrations, API Gateway automatically passes the backend output to the caller as the complete payload.\n但也許沒有很好理解，所以我們後面會實際打 Code 來示範，但我這邊先稍微解釋一下打開這個功能的影響:\n完整的 HTTP 請求資訊：啟用 Lambda Proxy Integration 後，API Gateway 會將整個 HTTP 請求打包成單一的 event object，並傳遞給 Lambda Function 簡化的處理流程：Lambda Function 接收到的是一個包含了所有請求資訊的 event object，因此不需要再通過模板或轉換就可以直接處理請求。這樣簡化了函數的輸入處理，因為 Lambda Function 已經包含了所有必要的請求資訊 控制 HTTP Response：Lambda Function 需要返回一個特定格式的物件，這個物件包括 Status Code、Response Headers 和 Response Body。由於 Response 是直接由 Lambda Function 生成的，所以開發者可以完全控制 Response 的格式和內容 效能和成本效益：由於請求處理的過程更加直接，減少了中間的處理和轉換步驟，可以提高處理效率，可能也會在某種程度上降低成本 總之，選擇 Lambda Proxy Integration 可以使得與 AWS Lambda 的整合更加緊密、直接和高效。這適合需要精細控制 HTTP 響應的場景，或者希望簡化 API 與 Lambda 之間交互的架構設計","title":"深度解析 AWS API Gateway 的 Lambda Proxy Integration 功能"},{"content":" Terraform Import 簡介 Terraform Import 是 Terraform 中的一個功能，允許你將現有的基礎設施資源導入到 Terraform 的狀態文件中。這樣，你可以將已有的基礎設施與 Terraform 的配置文件同步，並開始使用 Terraform 來管理這些資源\n什麼時候需要用到 terraform import？ terraform import 並不是一個自動偵測 AWS 資源上的東西，然後自動幫忙寫程式碼的工具 如果你以前沒有使用任何 IaC 的工具，都是直接在 AWS Console 上管理和創建資源，但現在想要導入 IaC，然後把那些「現有資源」導入到 Terraform 來管理，那你就會需要用到 terraform import\nterraform import 的主要目的是將已經存在的基礎設施資源導入到 Terraform 的狀態文件中，這樣你就可以用 Terraform 來管理這些資源。例如，如果你有一個已經存在的 AWS EC2 實例，而你現在想開始用 Terraform 來管理它，你需要導入這個實例以便 Terraform 知道它的存在和當前狀態\nSynopsis $ terraform import [options] ADDRESS ID ADDRESS 是 Terraform 配置中資源的地址。例如，aws_instance.my_instance ID 是資源的唯一標識符，這個值根據不同的資源類型會有所不同。例如，對於 AWS EC2 實例，這個 ID 是實例的 ID（例如 i-1234567890abcdef0） Example $ terraform import aws_instance.my_instance i-1234567890abcdef0 data.aws_region.current: Reading... data.aws_availability_zones.available: Reading... data.aws_ami.ubuntu: Reading... aws_instance.aws_linux: Import prepared! Prepared aws_instance for import aws_instance.aws_linux: Refreshing state... [id=i-00066e5627229c90e] data.aws_region.current: Read complete after 0s [id=us-east-1] data.aws_availability_zones.available: Read complete after 1s [id=us-east-1] data.aws_ami.ubuntu: Read complete after 1s [id=ami-0f81732f07ce19b1c] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. Hands-on 到 AWS Console 創建資源\n我這邊創建了一台 EC2 Instance\n接著在 main.tf 加入以下程式碼:\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { } 打開 Terminal 使用 terraform import 來 import 現有的資源\n$ terraform import aws_instance.aws_linux i-00066e5627229c90e data.aws_region.current: Reading... data.aws_availability_zones.available: Reading... data.aws_ami.ubuntu: Reading... aws_instance.aws_linux: Import prepared! Prepared aws_instance for import aws_instance.aws_linux: Refreshing state... [id=i-00066e5627229c90e] data.aws_region.current: Read complete after 0s [id=us-east-1] data.aws_availability_zones.available: Read complete after 1s [id=us-east-1] data.aws_ami.ubuntu: Read complete after 1s [id=ami-0f81732f07ce19b1c] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. 用 terraform state list 查看一下現在的 state，會看到我們的 aws_instance.aws_linux\n輸入看看 terraform plan :\n$ terrafomr plan ╷ │ Error: Missing required argument │ │ with aws_instance.aws_linux, │ on main.tf line 263, in resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34;: │ 263: resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { │ │ \u0026#34;launch_template\u0026#34;: one of `ami,instance_type,launch_template` must be specified ╵ ╷ │ Error: Missing required argument │ │ with aws_instance.aws_linux, │ on main.tf line 263, in resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34;: │ 263: resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { │ │ \u0026#34;ami\u0026#34;: one of `ami,launch_template` must be specified ╵ ╷ │ Error: Missing required argument │ │ with aws_instance.aws_linux, │ on main.tf line 263, in resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34;: │ 263: resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { │ │ \u0026#34;instance_type\u0026#34;: one of `instance_type,launch_template` must be specified 出現 ERROR 很正常，畢竟就是看著我們的程式碼去執行該指令，而我們的 resource \u0026quot;aws_instance\u0026quot; \u0026quot;aws_linux\u0026quot; {} 裡面都沒配置任何參數當然會出錯，要解決可以用 terraform state show 來處理，把這個資源的詳細資訊配置到我們程式碼，因為我們已經知道他被寫入 state\n使用 terraform state show 來查看資源的詳細資訊:\n$ terraform state show aws_instance.aws_linux # aws_instance.aws_linux: resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { ami = \u0026#34;ami-00beae93a2d981137\u0026#34; arn = \u0026#34;arn:aws:ec2:us-east-1:058264428816:instance/i-00066e5627229c90e\u0026#34; associate_public_ip_address = true availability_zone = \u0026#34;us-east-1b\u0026#34; cpu_core_count = 1 cpu_threads_per_core = 1 disable_api_stop = false disable_api_termination = false ebs_optimized = false get_password_data = false hibernation = false host_id = null # 略... 然後根據上面面 terraform state show 輸出的結果把程式碼改成如下:\n# main.tf resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { instance_type = \u0026#34;t2.micro\u0026#34; ami = \u0026#34;ami-00beae93a2d981137\u0026#34; } 接著再輸入 terraform plan 就不會報錯了，此時我們原先在 AWS Console 上手動建立了資源就被 Terraform 所管\n如果我們要刪掉那個資源也可以透過 terraform 來刪除，我們把原先那行註解掉:\n# main.tf # resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { # instance_type = \u0026#34;t2.micro\u0026#34; # ami = \u0026#34;ami-00beae93a2d981137\u0026#34; # } 接著我們再來執行 terraform plan :\n$ terraform plan # 略... # aws_instance.aws_linux will be destroyed # (because aws_instance.aws_linux is not in configuration) - resource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { - ami = \u0026#34;ami-00beae93a2d981137\u0026#34; -\u0026gt; null - arn = \u0026#34;arn:aws:ec2:us-east-1:058264428816:instance/i-00066e5627229c90e\u0026#34; -\u0026gt; null - associate_public_ip_address = true -\u0026gt; null - availability_zone = \u0026#34;us-east-1b\u0026#34; -\u0026gt; null - cpu_core_count = 1 -\u0026gt; null 補充 - 直接寫在程式碼內，import block 在 Terraform v1.5.0 之後，引入了新的 import block，import block 可以直接在 Terraform 配置文件中指定哪些資源需要被導入，而不需要單獨執行 terraform import 命令\n這個新功能的好處在於，當你和你的團隊需要多次或系統地導入資源時，可以將這些導入操作版本化並包含在基礎設施即程式碼 (IaC) 配置中\n一個 import block 的基本語法如下:\nimport { to = aws_instance.example id = \u0026#34;i-12345678\u0026#34; } to 指定了要導入到的 Terraform 資源塊 id 是現有資源的 ID 所以剛才我們用 CLI 所示範的內容，你可以改成用以下方式來撰寫\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;aws_linux\u0026#34; { # 你的資源配置 } import { to = aws_instance.example id = \u0026#34;i-00066e5627229c90e\u0026#34; } Resources Import | Terraform | HashiCorp Developer Shiun\u0026rsquo;s Learning Journal - 20240605 ","permalink":"https://shiun.me/blog/how-to-use-terraform-import-to-import-existing-resources-into-iac/","summary":"Terraform Import 簡介 Terraform Import 是 Terraform 中的一個功能，允許你將現有的基礎設施資源導入到 Terraform 的狀態文件中。這樣，你可以將已有的基礎設施與 Terraform 的配置文件同步，並開始使用 Terraform 來管理這些資源\n什麼時候需要用到 terraform import？ terraform import 並不是一個自動偵測 AWS 資源上的東西，然後自動幫忙寫程式碼的工具 如果你以前沒有使用任何 IaC 的工具，都是直接在 AWS Console 上管理和創建資源，但現在想要導入 IaC，然後把那些「現有資源」導入到 Terraform 來管理，那你就會需要用到 terraform import\nterraform import 的主要目的是將已經存在的基礎設施資源導入到 Terraform 的狀態文件中，這樣你就可以用 Terraform 來管理這些資源。例如，如果你有一個已經存在的 AWS EC2 實例，而你現在想開始用 Terraform 來管理它，你需要導入這個實例以便 Terraform 知道它的存在和當前狀態\nSynopsis $ terraform import [options] ADDRESS ID ADDRESS 是 Terraform 配置中資源的地址。例如，aws_instance.my_instance ID 是資源的唯一標識符，這個值根據不同的資源類型會有所不同。例如，對於 AWS EC2 實例，這個 ID 是實例的 ID（例如 i-1234567890abcdef0） Example $ terraform import aws_instance.","title":"要導入 IaC？用 Terraform Import 導入現有資源"},{"content":" 比賽簡介 這場比賽是 DIGITIMES 主辦，AWS 作為技術支援，總共邀請六個企業來命題，分為黑客組和創意交流組（簡單來說就是技術和非技術組），而黑客組在這場比賽必須使用 AWS 相關的服務和模型依照企業命題打造出 LLM Application 比賽前 賽前工作坊 在比賽前，比賽主辦方有舉辦多場賽前工作坊，讓我們熟悉 AI 技術，而我只要有空都有去參加，我把我在的工作坊的學習筆記都寫在我的 Notion 日誌\n2024/04/27 - 基礎 AI 工作坊 2024/04/28 - 基礎 AI 工作坊 2024/05/05 - 進階 AI 工作坊 (這場有 GameDay) 2024/05/07 - Gogoro 企業數據工作坊 其實 AI 這領域真的不是我的專長領域，我自己會開始開發 GenAI 應用是從加入伊雲谷後開始的，在伊雲谷執手的專案就是一個 LLM Application，加上 GenAI 話題真的是時下最火熱的話題，活在這時代，開發的日常要不碰到 AI 真的蠻難的\n而賽前工作坊的主講者們真的人都很好，講解的也都很清楚，特別感謝 Roger 和 Ginny 在工作坊的教導，以及 Scott 在 GameDay 當天賽後的支援，在賽前工作坊也和現場的人交流，認識了不少技術高手！\n比賽還沒開始，但光是前面的工作坊就覺得這次的參賽體驗真的很棒，賽前能跟這些技術大神學習，真的是一次難得的機會。\n比賽題目公布 在報名時是要填寫競賽主題的志願序的，我們當初投的第一個志願是「智慧應用」第二為「智慧移動 - Gogoro」，最終我們被安排在智慧移動的組別。\n而我們這組的題目如下: 官方提供的數據就是 Gogoro 官網上的車主手冊: https://support.gogoro.com/tw/manual/collections/236738689176894/\n基本上這個命題絕對逃不過 \u0026ldquo;RAG\u0026rdquo;\n這個題目有幾個難點:\n如何讓 LLM 回應圖片並且顯示出來? 車主手冊有很大的比例是圖片，如何把圖片 Embed? 車主手冊全是 pdf 檔，如何萃取出 pdf 檔內的圖片 當使用者提供的資訊不夠詳細，LLM 這邊應要求使用者提供更多資訊 何謂資訊足夠詳細? 該如何定義「是否詳細」 LLM 要怎麼知道還需要請使用者提供哪些額外資訊? 作戰會議 而在 2024/05/07 Gogoro 數據工作坊一結束，我們隊伍就馬上來開了第一場作戰會議，一直到比賽開始前，總共開了三次會議，我們大致把整個應用的架構想出來，以及要使用的技術棧都定義出來並做好分工。\n分工部分:\nYuna (隊長): 資料前處理及構建 Data Pipeline Richie: 資料前處理及構建 Data Pipeline Eason: Presentaion 及 Prompt 研究 Toby: 自動化將資料 Embedding ，然後自動化將向量儲存到 OpenSearch，以及利用 Gradio 構建介面 Shiun (我): LangChain 開發及部署 LLM 應用 架構部分應該看得出來我在部落格這裡塞不下，因此直接附上連結 (連結點我)，這邊所看到的架構圖只是我們會議討論時方便討論而畫出來的，但最後成品的架構其實跟這邊不太一樣，後面會提到最終的架構 另外想提一下，其實自己在開發 LLM 應用時，我很喜歡使用 Prompt Flow 來構建整個 Flow，雖然這是開源的，但這個終究是微軟的，所以最後這場比賽沒選擇使用 Prompt Flow XD，改成使用 LangGraph\n比賽前夕 我原本打算要用 LangGraph + LangChain 的組合來開發，但我花了三天學完 LangGraph 之後，發現自己真的沒有頭緒，用起來很力不從心，所以在比賽的前一晚我放棄使用 LangGraph，改成純用 Lambda 來構建出一個 Flow。\n而我擔心黑客松 30 個小時做不完，我在比賽的前一晚目標就是先開發出一個可以動的應用，然後在黑客松兩天串隊友們的資源。\n簡單來說，比賽前一晚我的目標是:\n串接 OpenSearch 實現 RAG 用 DynamoDB 儲存聊天紀錄，並確保 LLM 應用是具上下文記憶能力 IaC，確保黑客松當天直接一鍵部署，我是使用 AWS SAM 等最後弄完之後，大概早上 6:00 了，當時身體是累的但是睡不太著，然後就去買早餐，和隊友集合，然後去比賽啦\n其實當天我有開 Youtube 線上直播想要把黑客松前一晚的過程記錄起來\u0026hellip; 沒想到我 OBS 串流打開了，但是卻忘了按下「開始直播」\u0026hellip;一直到早上和隊友集合，隊友才告訴我我的 Youtube 畫面都是「等待中」\u0026hellip;\n比賽 Day1 - 一整天在 502 Bad Gateway 度過 早上和隊員一同進場報到 提案 在比賽剛開始時，評審會到各隊先看我們的初步提案給予我們建議，以我們這組「智慧移動 - Gogoro」來說，評審有: Gogoro 副總, Gogoro 資訊技術總監, Gogoro 資料科學家, AWS Sr. SA，但 Day1 提案的時候印象中是沒看到副總出席，而我們這隊就是把架構圖展示給評審看，同時也告訴我們遇到的難點。\n這個題目有幾個難點:\n如何讓 LLM 回應圖片並且顯示出來? 車主手冊有很大的比例是圖片，如何把圖片 Embed? 車主手冊全是 pdf 檔，如何萃取出 pdf 檔內的圖片 當使用者提供的資訊不夠詳細，LLM 這邊應要求使用者提供更多資訊 何謂資訊足夠詳細? 該如何定義「是否詳細」 LLM 要怎麼知道還需要請使用者提供哪些額外資訊? 當時我們只有請教評審們難點 4 的建議處理方式，而評審建議我的可以使用 Chain of Thought (CoT)\n開始卡在 502 Bad Gateway 在比賽才剛開始，我便遇到 502 Bad Gateway，明明早上 6.7 點左右都還 run 得好好的，現在 run 就會 502 Bad Gateway， 當時我遇到的 ERROR 如下:\n# 2024-05-18T01:29:19.802+-8:00 [ERROR] TypeError: not all arguments converted during string formatting Traceback (most recent call last): File \u0026#34;/var/task/is_question_relevant.py\u0026#34;, line 186, in lambda_handler response = retrieval_chain.invoke( # 略 當時我印象中我是為了使用 LangSmith 來方便追蹤我的 Chain，所以在 template.yaml 為 Lambda Function 配置了一些 LangChain 相關的環境變量，所以我當時對於程式碼是沒有任何改動的，完全就是在配置一些額外的環境變量，我完全不覺得設環境變量會造成錯誤。\n於是我就開始退 commit，一個一個慢慢往前追溯是哪邊開始造成的，最後把 template.yaml 的變更都捨棄 (也就是我配置環境變量那部分)，才發現就是真的因為配置了 LangChain 的環境變量導致發生錯誤\n以 OpenSearch Similarity Score Threshold 決定問題是否相關 在我們的架構中，有一個節點負責判斷用戶的問題是否與 Gogoro 的主題相關。如果問題不相關，我們會禮貌地拒絕回答。我們的策略是，當用戶的提問進來時，就我們會結合 Chat History 一起去做 Embedding 然後進行 Retrieval。我們設定了一個相似度分數閾值 (Similarity Score Threshold)，只有當文檔的 Similarity Score 高於這個閾值時，文檔才會被檢索出來。因此當沒有檢索到任何文檔，我們就可以判定用戶的提問是不相關的。\n而我們在 LangChain 官方文檔有看到這部分有 API 可以用，寫法會長這樣:\nretriever = db.as_retriever( search_type=\u0026#34;similarity_score_threshold\u0026#34;, search_kwargs={\u0026#34;k\u0026#34;: 3, \u0026#34;score_threshold\u0026#34;: 0.8}, ) docs_retrieved = retriever.invoke(query) for doc in docs_retrieved: print(\u0026#34;-\u0026#34; * 80) print(doc.page_content) print(\u0026#34;-\u0026#34; * 80) 但是很可惜我們稍早已經測試過，實際用下去就是會看到 NotImplementedError，簡單來說就是 LangChain 還沒有支援 OpenSearch 使用這個 similarity_score_threshold:\nBased on the information you\u0026rsquo;ve provided and the context from the LangChain repository, it seems like you\u0026rsquo;re encountering a NotImplementedError when trying to use the similarity_score_threshold search type with the OpenSearch retriever in LangChain. This is likely because the similarity_score_threshold search type is not currently supported in the OpenSearch retriever in the LangChain framework, as mentioned in this issue.\n資料來源: https://github.com/langchain-ai/langchain/issues/13007\n所以在這部分，只能放棄使用 LangChain，改成用原生的 Python 庫 opensearch-py，而很感謝我們隊員 Toby 對這部分還算熟悉，Toby 在比賽前幾日就有用過 opensearch-py 實踐 RAG (Toby 的 GitHub Repo 連結)，所以我省了很多研究時間。\n而別以為我們現在看起來很順利，我們雖然 Retrieval 這部分已經沒問題了，但接下來要再把這部分用 LangChain 的 chain.invoke() 又開始卡關了，這次碰到的 ERROR 是:\n[ERROR] ValueError: Invalid input type \u0026lt;class \u0026#39;langchain_core.prompts.chat.ChatPromptTemplate\u0026#39;\u0026gt;. Must be a PromptValue, str, or list of BaseMessages. Traceback (most recent call last): File \u0026#34;/var/task/is_question_rpy\u0026#34;, line 219, in lambda_handler answer = bedrock_llm.invoke(prompt) File \u0026#34;/var/task/langchalanguage_models/chat_models.py159, in invoke [self._convert_input(input)], File \u0026#34;/var/task/langchalanguage_models/chat_models.py142, in _convert_input raise ValueError( 但可惜到這邊，已經晚上 6:30 左右了，所以參賽者就先回家了，而我今天一整天就是「始於 502, 終於 502」，人還沒睡覺然後第一天東西還沒辦法正常 run 心態簡直快崩潰\u0026hellip;\n一回到家心裡想著一定要把這解決才能睡，結果一到家一碰到床之後睜開眼已經是 Day2 早上 6:00\n比賽 Day2 - 可敬的對手 一時來的靈感 黑客組預計會在 Day2 14:40 結束比賽\nDay2 我早上 6:00 醒來，梳洗一下，也不知道為什麼\u0026hellip; 靈感很臨時來，突然覺得我想到 Day1 Bug 的解法了，馬上打開電腦試了一下，還真的解出來了。\n真的平常工作也是這樣，卡了一整天的 Bug，總是會在無意間想到解法，我也算這次很幸運這個臨感來的那麼快，在 Day2 的一早就馬上解決昨天的大難關，給 Day2 做了一個美好的開始。\n現在我們的系統可以說是可以動了，放下身上的重擔後，我就趕緊搭車去黑客松比賽現場與隊友會合了。\n將資源部署到黑客松的 AWS 帳號 其實在 Day1 所有開發的東西我都部署在自己的 AWS 帳號，因為整個專案我都是用 AWS SAM來建置和部署資源的，所以要把同樣的東西部署到別的 AWS 帳號相當容易。\n但當然也是沒那麼順利，過程我犯了一個很傻的錯誤，就是忘記把 samconfig.toml 裡面的 image_repositories 換掉，所以我切換到黑客松的環境後，輸入 sam deploy，我根本沒辦法 Push 新的 Image 上去 ECR，因為我現在的 Credential 就是沒權限去 Push Image 到我自己 AWS 帳號的 ECR\n我以為是黑客松的 AWS 環境給的 Credential 有問題，所以還跑去請教 AWS SA - Scott，結果 SA 一提點我就知道自己犯蠢 XD，真的很抱歉不小心打擾到 SA\n發現自己犯蠢後資源當然也部署上去了，只能說 IaC 真的讚！\nPresentation 接下來我們把資源全都部署好，Toby 用 Gradio 切了一個簡單的前端介面然後我再把前端部署上去我們的專案就大功告成了！\nEason 將會負責上台簡報，而我會負責操控電腦\n在這兩天的黑客松，Eason 一直在旁邊研究我們的系統以及跟我們釐清很多技術架構，然後為我們這兩天的成品做了一個很棒的簡報\n比賽 14:40 結束後，我們收到了 Gogoro 指定的問題，必須在等下的 Presentation 中進行 Live Demo。而我們「大使夢之隊」是「智慧移動」第一個要上台報告的隊伍。\n沒過多久後，就開始上台簡報了，只能說 Eason 的台風真的很穩，邏輯清晰，把我們系統的價值和架構表達得很清楚。另外當時很多大使來旁邊幫我們加油，到了 Live Demo 環節，我們第一題不知什麼原因就 Timeout 了，但網頁重整後又正常了，真的有 Live Demo 就一定要擺綠色乖乖\u0026hellip;而後面的幾個問題，我覺得都符合我們預期，但就是有發現我們系統的回應時間其實太久了，幾乎都 20 秒上下才完成。\n但整體來說，我覺得我們已經把我們想展示的東西都展示出來了。\n可敬的對手 在智慧移動這組中，我們遇到了一個勁敵——「富貴怎麼先走了」。他們的系統架構大概跟我們有七八成相似，但在前端的表現以及 LLM 的回應速度上，他們明顯勝出很多。當他們上台展示時，我們都非常佩服他們 LLM 的回應速度如此之快，前端頁面做得既舒服又有流暢的 Steaming，那個第一印象絕對是超越我們的。\n當時我就知道，他們很可能是這次比賽的第一名。比賽結果出爐前，對方也來和我們交流他們使用的技術。我們從賽前工作坊的 GameDay 就知道他們實力雄厚，因為他們那組在 GameDay 也拿了第一！\n上圖為「富貴怎麼先走了」當天的 Presentation\n最終結果也不出所料，「富貴怎麼先走了」贏得了智慧移動組的優勝！比賽結束後，我們和他們聊了聊，發現他們真的很厲害，分工相當明確，隊友間彼此都很信任。這次經驗讓我們學到了很多，也再次恭喜他們取得優勝！\n結語 這場比賽身邊的人都很看好我們的大使夢之隊，也很感謝大家來現場加油打氣和餵食，不過最後沒能取得優勝確實有些遺憾！尤其是「富貴怎麼先走了」這隊真的很強，讓我認識到自己的實力還有待提升。他們的成品也給了我很多啟發，讓我知道自己的系統有哪些可以改進的地方。\n這次比賽學到了很多，主辦方的賽前工作坊非常紮實，現場的 SA 技術支援也非常到位，透過和各隊的交流，我也學到了不少新的技巧。\n最後，真的超感謝隊友們。資料前處理部分，隊長 Yuna 和 Richie 真的是超級給力！Toby 本身在做 LLM 方面的研究，他在整個過程中提供了我心目中的 Best Practice！Eason 比賽全程在一旁觀察我們的開發，研究 Prompt 並釐清整體的系統架構，簡報時他更是把系統完整呈現出來！每個隊友都是核心，能和這樣的隊友在 30 個小時內打造出應用，真的是一段很有革命情感的經歷，希望之後還有機會能再一起組隊參賽。\n","permalink":"https://shiun.me/blog/2024-aws-genai-hackathon/","summary":"比賽簡介 這場比賽是 DIGITIMES 主辦，AWS 作為技術支援，總共邀請六個企業來命題，分為黑客組和創意交流組（簡單來說就是技術和非技術組），而黑客組在這場比賽必須使用 AWS 相關的服務和模型依照企業命題打造出 LLM Application 比賽前 賽前工作坊 在比賽前，比賽主辦方有舉辦多場賽前工作坊，讓我們熟悉 AI 技術，而我只要有空都有去參加，我把我在的工作坊的學習筆記都寫在我的 Notion 日誌\n2024/04/27 - 基礎 AI 工作坊 2024/04/28 - 基礎 AI 工作坊 2024/05/05 - 進階 AI 工作坊 (這場有 GameDay) 2024/05/07 - Gogoro 企業數據工作坊 其實 AI 這領域真的不是我的專長領域，我自己會開始開發 GenAI 應用是從加入伊雲谷後開始的，在伊雲谷執手的專案就是一個 LLM Application，加上 GenAI 話題真的是時下最火熱的話題，活在這時代，開發的日常要不碰到 AI 真的蠻難的\n而賽前工作坊的主講者們真的人都很好，講解的也都很清楚，特別感謝 Roger 和 Ginny 在工作坊的教導，以及 Scott 在 GameDay 當天賽後的支援，在賽前工作坊也和現場的人交流，認識了不少技術高手！\n比賽還沒開始，但光是前面的工作坊就覺得這次的參賽體驗真的很棒，賽前能跟這些技術大神學習，真的是一次難得的機會。\n比賽題目公布 在報名時是要填寫競賽主題的志願序的，我們當初投的第一個志願是「智慧應用」第二為「智慧移動 - Gogoro」，最終我們被安排在智慧移動的組別。\n而我們這組的題目如下: 官方提供的數據就是 Gogoro 官網上的車主手冊: https://support.gogoro.com/tw/manual/collections/236738689176894/\n基本上這個命題絕對逃不過 \u0026ldquo;RAG\u0026rdquo;","title":"學生時期的最後一場比賽: 2024 GenAI Hackathon 比賽紀錄"},{"content":" 前幾天寫了一篇 Serverless Framework 101，今天就來寫寫 AWS SAM 的教學，這兩個都是用來部署及管理 Serverless 應用的框架，兩者可以說是競爭對手關係！待之後有空再來寫一篇這兩個產品的比較\nPrerequisites 註冊 AWS 帳戶 建立 Admin IAM User 建立 access key ID and secret access key 安裝 AWS CLI 配置 AWS credentials 以上詳細內容請查看官方文檔: prerequisites\n安裝 AWS SAM CLI Mac 的用戶要注意一下，從 2023/9 開始，AWS 不會在維護 AWS SAM CLI 的 Homebrew Installer\n由於我現在是使用 Windows 作業系統的電腦，今天示範如何在 Windows 安裝 AWS SAM CLI\nWindows 安裝 Windows 安裝相當簡單，只要去官方文檔裡面下載 MSI File，接著無腦的 Next 按按按就裝好了 XD\n下載好之後，輸入指令 sam --version 檢查是否安裝成功\n$ sam --version SAM CLI, version 1.115.0 Windows 啟用 LongPathsEnabled 到這邊還沒有結束，對於 Windows 用戶，Windows 系統的最大路徑限制（MAX_PATH）通常是 260 個字符，請一定要啟用 LongPathsEnabled ，不然在 sam 的某些指令執行後會因為文件路徑過長出現 Error，例如: sam init\nStarting in Windows 10, version 1607, MAX_PATH limitations have been removed from common Win32 file and directory functions. However, you must opt-in to the new behavior.\n資料來源: Maximum Path Length Limitation - Win32 apps\n請你以系統管理員身分打開你的 Powershell，這邊我使用 Powershell 版本為 7.4.2\n輸入指令:\n$ New-ItemProperty -Path \u0026#34;HKLM:\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\u0026#34; -Name \u0026#34;LongPathsEnabled\u0026#34; -Value 1 -PropertyType DWORD -Force LongPathsEnabled : 1 PSPath : Microsoft.PowerShell.Core\\Registry::HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem PSParentPath : Microsoft.PowerShell.Core\\Registry::HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control PSChildName : FileSystem PSDrive : HKLM PSProvider : Microsoft.PowerShell.Core\\Registry 因為有些 process 可能在設置此鍵之前就已經緩存，為了讓系統上的所有應用程序識別這個鍵的值，請重新開機！\nHello World Application 建立一個 Project 首先我們先建立一個 Project\n$ sam init SAM CLI now collects telemetry to better understand customer needs. You can OPT OUT and disable telemetry collection by setting the environment variable SAM_CLI_TELEMETRY=0 in your shell. Thanks for your help! Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html You can preselect a particular runtime or package type when using the `sam init` experience. Call `sam init --help` to learn more. Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Choose an AWS Quick Start application template 1 - Hello World Example 2 - Data processing 3 - Hello World Example with Powertools for AWS Lambda 4 - Multi-step workflow 5 - Scheduled task 6 - Standalone function 7 - Serverless API 8 - Infrastructure event management 9 - Lambda Response Streaming 10 - Serverless Connector Hello World Example 11 - Multi-step workflow with Connectors 12 - GraphQLApi Hello World Example 13 - Full Stack 14 - Lambda EFS example 15 - DynamoDB Example 16 - Machine Learning Template: 1 Use the most popular runtime and package type? (Python and zip) [y/N]: y Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: Would you like to enable monitoring using CloudWatch Application Insights? For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: Project name [sam-app]: aws-sam-101 Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment) ----------------------- Generating application: ----------------------- Name: aws-sam-101 Runtime: python3.9 Architectures: x86_64 Dependency Manager: pip Application Template: hello-world Output Directory: . Configuration file: aws-sam-101\\samconfig.toml Next steps can be found in the README file at aws-sam-101\\README.md Commands you can use next ========================= [*] Create pipeline: cd aws-sam-101 \u0026amp;\u0026amp; sam pipeline init --bootstrap [*] Validate SAM template: cd aws-sam-101 \u0026amp;\u0026amp; sam validate [*] Test Function in the Cloud: cd aws-sam-101 \u0026amp;\u0026amp; sam sync --stack-name {stack-name} --watch 現在我們已經成功建立好專案\n我們進到裡面看一下\n$ cd aws-sam-101 $ tree │ .gitignore │ README.md │ samconfig.toml # 存參數 │ template.yaml # AWS 根據此檔案配置你的 Infra │ __init__.py │ ├─events │ event.json │ ├─hello_world │ app.py # 你的 Lambda Function 寫在這 │ requirements.txt │ __init__.py │ └─tests │ requirements.txt │ __init__.py │ ├─integration │ test_api_gateway.py │ __init__.py │ └─unit test_handler.py __init__.py Build 接下來我們就要來打包我們專案了，但因為我想要使用 Python 3.11，所以我先到 template.yaml 把 Runtime 改成 3.11\n接著輸入以下指令\n$ sam build Starting Build use cache Manifest file is changed (new hash: 3298f13049d19cffaa37ca931dd4d421) or dependency folder (.aws-sam\\deps\\ab6747e1-a68c-4fab-ae91-fa1c4dcd23e1) is missing for (HelloWorldFunction), downloading dependencies and copying/building source Building codeuri: C:\\GitHub\\aws-sam-101\\hello_world runtime: python3.11 metadata: {} architecture: x86_64 functions: HelloWorldFunction Running PythonPipBuilder:CleanUp Running PythonPipBuilder:ResolveDependencies Running PythonPipBuilder:CopySource Running PythonPipBuilder:CopySource Build Succeeded Built Artifacts : .aws-sam\\build Built Template : .aws-sam\\build\\template.yaml Commands you can use next ========================= [*] Validate SAM template: sam validate [*] Invoke Function: sam local invoke [*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch [*] Deploy: sam deploy --guided 如指令 output 所示，你會發現你的 .aws-sam 目錄下多了 build 這個目錄\n.aws-sam ├── build │ ├── HelloWorldFunction │ │ ├── __init__.py │ │ ├── app.py # Lambda Function │ │ └── requirements.txt │ └── template.yaml └── build.toml Deploy 在這個部份，你需要配置你的 AWS Credentials，我已經事先創建好一個 IAM User 來暫時用，詳細如何配置你的 AWS Credentials 請自行翻閱官方文檔，本文預設讀者已具備操作 AWS 的基本能力\n輸入以下指令來部署你的 Lambda\n$ sam deploy --guided Configuring SAM deploy ====================== Looking for config file [samconfig.toml] : Found Reading default arguments : Success Setting default arguments for \u0026#39;sam deploy\u0026#39; ========================================= Stack Name [aws-sam-101]: AWS Region [ap-northeast-1]: #Shows you resources changes to be deployed and require a \u0026#39;Y\u0026#39; to initiate deploy Confirm changes before deploy [Y/n]: n #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation [Y/n]: #Preserves the state of previously provisioned resources when an operation fails Disable rollback [y/N]: HelloWorldFunction has no authentication. Is this okay? [y/N]: y Save arguments to configuration file [Y/n]: SAM configuration file [samconfig.toml]: SAM configuration environment [default]: Looking for resources needed for deployment: Creating the required resources... Successfully created! Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-jptiw4noplqk A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False Parameter \u0026#34;stack_name=aws-sam-101\u0026#34; in [default.deploy.parameters] is defined as a global parameter [default.global.parameters]. This parameter will be only saved under [default.global.parameters] in C:\\GitHub\\aws-sam-101\\samconfig.toml. Saved arguments to config file Running \u0026#39;sam deploy\u0026#39; for future deployments will use the parameters saved above. The above parameters can be changed by modifying samconfig.toml Learn more about samconfig.toml syntax at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html Uploading to aws-sam-101/37624213qqc910da9321690a64a28 554765 / 554765 (100.00%) Deploying with following values =============================== Stack name : aws-sam-101 Region : ap-northeast-1 Confirm changeset : False Disable rollback : False Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-jptiw4noplqk Capabilities : [\u0026#34;CAPABILITY_IAM\u0026#34;] Parameter overrides : {} Signing Profiles : {} Initiating deployment ===================== Uploading to aws-sam-101/dbd54debe319zaa19577cbf2egaj4.template 1257 / 1257 (100.00%) Waiting for changeset to be created.. CloudFormation stack changeset ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Operation LogicalResourceId ResourceType Replacement ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Add HelloWorldFunctionHelloWorldPermissionPro AWS::Lambda::Permission N/A d + Add HelloWorldFunctionRole AWS::IAM::Role N/A + Add HelloWorldFunction AWS::Lambda::Function N/A + Add ServerlessRestApiDeployment47fcad5f9d AWS::ApiGateway::Deployment N/A + Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A + Add ServerlessRestApi AWS::ApiGateway::RestApi N/A ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:1234556781:changeSet/samcli-deploy17144221345/7ac52521a5-34ce-432f-bb1e-64521743e8g 2024-04-28 21:27:31 - Waiting for stack create/update to complete CloudFormation events from stack operations (refresh every 5.0 seconds) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CREATE_IN_PROGRESS AWS::CloudFormation::Stack aws-sam-101 User Initiated CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Eventual consistency check initiated CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi - CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi - CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment432d5f9d - CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermissionPro - d CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction - CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermissionPro Resource creation Initiated d CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment4715f9d Resource creation Initiated CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermissionPro - d CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment12d5f9d - CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage - CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage - CREATE_COMPLETE AWS::CloudFormation::Stack aws-sam-101 - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::1234566781:role/aws-sam-101-HelloWorldFunctionRole-WAAUK9mx1X1E Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://ffj14dx1k.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:ap-northeast-1:1234567890:function:aws-sam-101-HelloWorldFunction-714agg15dlfg -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Successfully created/updated stack - aws-sam-101 in ap-northeast-1 當你看到 Successfully created/updated stack - aws-sam-101 in ap-northeast-1 就是完成囉\n我們到 AWS Console 查看一下 Lambda 和 CloudFormation\n而 sam deploy 這個指令背後執行的具體步驟如下:\n創建 S3 Bucket 並上傳 .aws-sam 目錄： AWS SAM CLI 首先創建一個 S3 Bucket（如果沒有指定現有的 Bucket）。這個 Bucket 用於存儲部署過程中需要的所有文件。 接著，AWS SAM CLI 將你的 .aws-sam 目錄上傳到這個新建的 S3 Bucket 中。.aws-sam 目錄通常包含編譯和打包後的應用程式碼及其依賴文件，這些是部署到 AWS 的必需資源。 將 AWS SAM 模板轉換為 AWS CloudFormation 並上傳： AWS SAM 模板是一種描述你的服務器無應用架構的文件，它使用 YAML 或 JSON 格式編寫。AWS SAM CLI 會將這個模板轉換成 AWS CloudFormation 模板。CloudFormation 是 AWS 提供的一個服務，允許用戶通過編寫模板來模型化和設定整個 AWS 資源堆棧。 轉換後的模板隨後被上傳到 AWS CloudFormation 服務。這個步驟是為了準備資源的配置和管理。 AWS CloudFormation 佈置資源： 一旦模板上傳到 AWS CloudFormation 服務，CloudFormation 便開始根據模板中的定義來創建和配置所需的 AWS 資源。這包括設定如函數、資料庫、網路設置等必要的組件。 CloudFormation 確保所有資源都按照模板中定義的依賴關係和參數設定正確部署，並管理資源的整個生命周期。 調用部署上去的 Lambda Function 現在我們可以來測試看看 API Endpoint，到剛剛的 Lambda，點擊 API Gateway 找到 Endpoint\n$ curl {YOUR_API_ENDPOINT} {\u0026#34;message\u0026#34;: \u0026#34;hello world\u0026#34;} 除了直接到 AWS Console 查看 API Endpoint 之外，sam 還有提供指令來查看 API Endpoints\n$ sam list endpoints --output json [ { \u0026#34;LogicalResourceId\u0026#34;: \u0026#34;HelloWorldFunction\u0026#34;, \u0026#34;PhysicalResourceId\u0026#34;: \u0026#34;aws-sam-101-HelloWorldFunction-7111ffa\u0026#34;, \u0026#34;CloudEndpoint\u0026#34;: \u0026#34;-\u0026#34;, \u0026#34;Methods\u0026#34;: \u0026#34;-\u0026#34; }, { \u0026#34;LogicalResourceId\u0026#34;: \u0026#34;ServerlessRestApi\u0026#34;, \u0026#34;PhysicalResourceId\u0026#34;: \u0026#34;123j013dx3i\u0026#34;, \u0026#34;CloudEndpoint\u0026#34;: [ \u0026#34;https://12313d13i.execute-api.ap-northeast-1.amazonaws.com/Prod\u0026#34;, \u0026#34;https://12313d13i.execute-api.ap-northeast-1.amazonaws.com/Stage\u0026#34; ], \u0026#34;Methods\u0026#34;: [ \u0026#34;/hello[\u0026#39;get\u0026#39;]\u0026#34; ] } ] 另外我們也可以使用 sam remote invoke 指令來調用部署 AWS 上的 Lambda\n但請特別注意，invoke 後面所接的參數，是你在 template.yaml 中所定義的 Resources 名稱，也就是 HelloWorldFunction，這跟 Serverless Framework 的 serverless invoke 指令概念一樣\n關於上述提到的 Serverless Framework，我有寫一篇教學文章，裡面有提到 serverless invoke 指令 (連結)\n$ sam remote invoke HelloWorldFunction Invoking Lambda Function HelloWorldFunction START RequestId: 1ec7cb08-1066-4f23-b4fd-b542a97ef27b Version: $LATEST END RequestId: 1ec7cb08-1066-4f23-b4fd-b542a97ef27b REPORT RequestId: 1ec7cb08-1066-4f23-b4fd-b542a97ef27b Duration: 2.30 ms Billed Duration: 3 ms Memory Size: 128 MB Max Memory Used: 33 MB Init Duration: 85.18 ms {\u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;{\\\u0026#34;message\\\u0026#34;: \\\u0026#34;hello world\\\u0026#34;}\u0026#34;} 對程式碼做個小更改，並快速部署最新變更至 AWS 現在我們來稍微修改一下 Lambda 程式碼，我把 hello world 改成 → hello shiun\n也許第一時間我們會想說可以用 sam deploy 這個指令把最新的變更部署上去，但是 AWS SAM CLI 還提供了一個指令 — sam snyc ，當正在開發 AWS Lambda 函數或其他 AWS 資源並且需要頻繁進行小的更改時，sam sync 可以讓您快速將這些更改推送到 AWS\nsam deploy vs sam sync sam sync 允許開發者快速將本地更改同步到已部署的應用，特別是對於代碼和配置的小修改。這對於快速開發周期非常有用，因為它大幅減少了等待時間，開發者可以即時看到他們更改的效果 只更新有變更的資源，避免了不必要的重複部署過程，這樣可以節省時間和成本，尤其是在開發階段的頻繁更新中 sam deploy： 進行完整的部署，包括重新打包應用、上傳到 S3，並通過 AWS CloudFormation 更新整個 Stack。這個過程通常比較耗時，對於小幅度的迭代來說可能效率不高 每次部署都可能涉及重建和重啟所有資源，即使是未更改的部分也一樣，這會導致更高的時間和成本消耗 綜合以上，我們這裡修改了程式碼，比較好的做法是使用 sam sync 指令來部署最新變更\n$ sam sync --watch The SAM CLI will use the AWS Lambda, Amazon API Gateway, and AWS StepFunctions APIs to upload your code without performing a CloudFormation deployment. This will cause drift in your CloudFormation stack. **The sync command should only be used against a development stack**. Confirm that you are synchronizing a development stack. Enter Y to proceed with the command, or enter N to cancel: [Y/n]: Y Queued infra sync. Waiting for in progress code syncs to complete... Starting infra sync. Manifest is not changed for (HelloWorldFunction), running incremental build Building codeuri: C:\\GitHub\\aws-sam-101\\hello_world runtime: python3.11 metadata: {} architecture: x86_64 functions: HelloWorldFunction Running PythonPipBuilder:CopySource Build Succeeded Successfully packaged artifacts and wrote output template to file C:\\Users\\Shiun\\AppData\\Local\\Temp\\tmpve9a9m2j. Execute the following command to deploy the packaged template sam deploy --template-file C:\\Users\\Shiun\\AppData\\Local\\Temp\\tmpveam2j --stack-name \u0026lt;YOUR STACK NAME\u0026gt; Deploying with following values =============================== Stack name : aws-sam-101 Region : ap-northeast-1 Disable rollback : False Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-jptialqk Capabilities : [\u0026#34;CAPABILITY_NAMED_IAM\u0026#34;, \u0026#34;CAPABILITY_AUTO_EXPAND\u0026#34;] Parameter overrides : {} Signing Profiles : null Initiating deployment ===================== 2024-04-28 22:15:56 - Waiting for stack create/update to complete CloudFormation events from stack operations (refresh every 0.5 seconds) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- UPDATE_IN_PROGRESS AWS::CloudFormation::Stack aws-sam-101 User Initiated UPDATE_IN_PROGRESS AWS::CloudFormation::Stack aws-sam-101 Transformation succeeded CREATE_IN_PROGRESS AWS::CloudFormation::Stack AwsSamAutoDependencyLayerNestedStack - CREATE_IN_PROGRESS AWS::CloudFormation::Stack AwsSamAutoDependencyLayerNestedStack Resource creation Initiated CREATE_COMPLETE AWS::CloudFormation::Stack AwsSamAutoDependencyLayerNestedStack - UPDATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction - UPDATE_COMPLETE AWS::Lambda::Function HelloWorldFunction - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS AWS::CloudFormation::Stack aws-sam-101 - UPDATE_COMPLETE AWS::CloudFormation::Stack aws-sam-101 - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::1234567890:role/aws-sam-101-HelloWorldFunctionRole-WAAUK9mx1X1E Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://g13i1111.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:ap-northeast-1:1234567890:function:aws-sam-101-HelloWorldFunction-7i6w81lfg -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Stack update succeeded. Sync infra completed. CodeTrigger not created as CodeUri or DefinitionUri is missing for ServerlessRestApi. Infra sync completed. 現在我們上去 AWS Console 查看一下 Lambda 和 CloudFormation 的變更\nLambda 已經成功修改這部分我想沒什麼問題，那 CloudFormation 的 Stack 顯示 \u0026ldquo;NESTED” 這是什麼？由於此文章主要以 AWS SAM 入門教學為主，我這邊簡單解釋：\n在 AWS CloudFormation 中，Nested Stack 就是在一個主要的 Stack（想像成一個大的項目列表）裡面，可以創建和管理多個小 Stack（就像是項目列表中的子列表）。這樣做的好處是，當你有很多相似的設置或配置需要重複使用時，你可以把這些配置做成一個小 Stack，然後在其他項目中引用它，這樣就不需要每次都重寫相同的配置，可以讓整個結構更清晰，也更容易管理。\n承上，由於這個特性，我們可以重用配置，這也是為什麼 sam sync 比較適合開發快速迭代，而且部署速度較快\n詳細請見 AWS 官方文檔: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html\n再次調用一次，看看變更\n$ curl {YOUR_API_ENDPOINT} {\u0026#34;message\u0026#34;: \u0026#34;hello shiun\u0026#34;} 透過 AWS SAM CLI 刪除部署在 AWS 上的資源 CloudFormation 的特性就是可以刪除 Stack 來把當初透過這個 Stack 創建出來的資源刪乾淨，而 AWS SAM 身為 CloudFormation 的拓展，也提供了 sam delete 這個指令來刪除該專案部署在雲端上的資源\n$ sma delete Are you sure you want to delete the stack aws-sam-101 in the region ap-northeast-1 ? [y/N]: y Do you want to delete the template file 3141123123abc82d45ef55faf04.template in S3? [y/N]: y - Deleting S3 object with key 4c36d2ab78169da9e38fe8339b80626a - Deleting S3 object with key bd8e9f5130e915b480c3b2279a8baedb.template - Deleting S3 object with key 21410647a9e8cabc82d45e5556b6a804.template - Deleting Cloudformation stack aws-sam-101 Deleted successfully 其他資源 Shiun Blog - Serverless Framework 101 - 輕鬆開發並快速部署你的 AWS Lambda AWS Workshop Studio - AWS SAM Serverless Patterns Collection AWS Docs - Working with nested stacks ","permalink":"https://shiun.me/blog/aws-sam-101/","summary":"前幾天寫了一篇 Serverless Framework 101，今天就來寫寫 AWS SAM 的教學，這兩個都是用來部署及管理 Serverless 應用的框架，兩者可以說是競爭對手關係！待之後有空再來寫一篇這兩個產品的比較\nPrerequisites 註冊 AWS 帳戶 建立 Admin IAM User 建立 access key ID and secret access key 安裝 AWS CLI 配置 AWS credentials 以上詳細內容請查看官方文檔: prerequisites\n安裝 AWS SAM CLI Mac 的用戶要注意一下，從 2023/9 開始，AWS 不會在維護 AWS SAM CLI 的 Homebrew Installer\n由於我現在是使用 Windows 作業系統的電腦，今天示範如何在 Windows 安裝 AWS SAM CLI\nWindows 安裝 Windows 安裝相當簡單，只要去官方文檔裡面下載 MSI File，接著無腦的 Next 按按按就裝好了 XD\n下載好之後，輸入指令 sam --version 檢查是否安裝成功","title":"AWS SAM 101 - 文長圖多！從安裝到部署你的 AWS Lambda"},{"content":"Serverless Framework 簡介 Serverless Framework 是一個開源的無服務器應用框架，它允許開發者快速建立、部署和管理在 AWS Lambda、Google Cloud Functions、Azure Functions 等雲平台上運行的無服務器應用。這個框架使用一個簡潔的配置文件（通常是 serverless.yml），在其中定義了應用的所有資源和設定，讓開發者可以專注於編寫業務邏輯而非管理基礎設施。\n安裝 Serverless Framework Prerequisites:\n需要有 npm，若你的電腦沒有 npm，請去下載 NodeJS $ npm install -g serverless 創建一個 Service $ serverless ? What do you want to make? AWS - Node.js - Starter AWS - Node.js - HTTP API AWS - Node.js - Scheduled Task AWS - Node.js - SQS Worker AWS - Node.js - Express API AWS - Node.js - Express API with DynamoDB AWS - Python - Starter \u0026gt; AWS - Python - HTTP API AWS - Python - Scheduled Task AWS - Python - SQS Worker AWS - Python - Flask API AWS - Python - Flask API with DynamoDB Other 輸入指令後透過方向鍵選取你要的 Template，本文將已 AWS - Python HTTP API 示範\n如果這些模板沒有你滿意的，可以到 serverless/examples 找你要的模板，然後指令輸入 serverless --template-url=https://github.com/serverless/examples/tree/v3/...\n? What do you want to call this project? serverless-framework-101 ✔ Project successfully created in serverless-framework-101 folder ? Register or Login to Serverless Framework Yes Logging into the Serverless Framework via the browser If your browser does not open automatically, please open this URL: https://app.serverless.com?client=cli\u0026amp;transactionId=x6r7NmzVa-ExeVdCyQZV2 ✔ You are now logged into the Serverless Framework ✔ Your project is ready to be deployed to Serverless Dashboard (org: \u0026#34;shiunchiu\u0026#34;, app: \u0026#34;serverless-framework-101\u0026#34;) 進入你的工作目錄，然後開啟 VS Code\n$ cd your-service-name $ code . 現在的目錄架構應該會長這樣\nC:. .gitignore handler.py README.md serverless.yml 配置 Provider 登入 AWS 帳號 請先登入你的 AWS 帳號\n到 Serverless Framework Dashboard 配置 Provider 按下 \u0026ldquo;Connect AWS Provider\u0026rdquo; 後，會跳轉到 AWS CloudFormation 頁面，直接下滑到底，打勾 “I acknowledge that AWS CloudFormation might create IAM resources with custom names.” → Create Stack 等待一下，等 Stack 創建好我們可以去看一下這個 Stack，其實他就是幫你自動創建一個 IAM Role Stack 創建好後，跳轉到 Serverless Framework Dashboard，就會看到創建好的 Provider\n寫好 Code，把你的 Code 部署到 AWS 當我們寫好 Code 之後，就要把 Code 部署到 AWS 上面，這部分就來教學如何透過 Serverless Framework CLI 來部署\n$ serverless deploy # or sls deploy Deploying serverless-framework-101 to stage dev (us-east-1, \u0026#34;serverless-framework-101-dev\u0026#34; provider) Warning: Serverless Framework observability features do not support the following runtime: python3.11 ✔ Serverless Framework\u0026#39;s Observability features being set up on your AWS account (one-time set-up). An email will be sent upon completion, or view progress within the Dashboard: https://app.serverless.com/shiunchiu/settings/integrations ✔ Serverless Framework Observability is enabled ✔ Service deployed to stack serverless-framework-101-dev (173s) dashboard: https://app.serverless.com/shiunchiu/apps/serverless-framework-101/serverless-framework-101/dev/us-east-1 endpoint: GET - https://1ibju7jfc9.execute-api.us-east-1.amazonaws.com/ functions: hello: serverless-framework-101-dev-hello (2 kB) 如果比較懶想使用 sls deploy 指令，但你又使用 Powershell ，有可能會如果看到這種輸出，這是因為 PowerShell 將 sls 誤認為是 Select-String 的別名，這是 PowerShell 中用於搜索字符串的內建命令。建議換個 git bash 或是就乖乖打完整指令\n$ sls deploy cmdlet Select-String at command pipeline position 1 Supply values for the following parameters: Path[0]: 到 AWS Console 查看部署結果 我們可以先到 Lambda 頁面，可以看到部署上去的 function\n因為 Serverless Framework 其實是透過 CloudFormation 來部署，現在我們到 CloudFormation 頁面查看，也會看到有新的 Stack\n調用 Lambda Function 當我們部署好之後，就可以來調用看看!\n$ serverless invoke -f hello { \u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;{\\\u0026#34;message\\\u0026#34;: \\\u0026#34;Go Serverless v3.0! Your function executed successfully!\\\u0026#34;, \\\u0026#34;input\\\u0026#34;: {}}\u0026#34; } 透過 serverless invoke 指令就可以調用我們部署上去的 Lambda\n但需要特別注意 -f 後面的值是要根據你在 serverless.yml 上面所配置的 function name 來設定，並非已經部署上去的 Lambda Function name\n現在我們上去 CloudWatch 看 Log，會看到我們剛剛的調用\n有時候我們會想要直接在 Local 看到 log，那可以剛剛的 serverless invoke 指令加上 --log ，這樣就不用到 AWS Console 查看了\n$ serverless invoke -f hello --log { \u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;{\\\u0026#34;message\\\u0026#34;: \\\u0026#34;Go Serverless v3.0! Your function executed successfully!\\\u0026#34;, \\\u0026#34;input\\\u0026#34;: {}}\u0026#34; } -------------------------------------------------------------------- START SERVERLESS_TELEMETRY.TZ.H4sIAPFVLWYC/43QMUoDQRTGcSO4wpYBEbcxhIAYmGVmZ+bNTDoFG7EzlSA6+2aWhGyyYTduIuIhvELO4B0EC4/gBSy8glELLYTYveLBx+8f1mEHEETmPRLDJCUCHRAjE0pMmjqQjnKPNmpXvqx9mfuqIllpx35elCPCKCPO12Tg87zodsO9X192XpHcjlNnSeVGzS0a8ziJguntbFBMoqdG2NKWKp6i4AoFGOtQayW4BKe4FYkRzVaK4JGjlhI48NRpzsAw7TMJic5k1g5XK/H3yuHB69v+Y+95l928vH8dPVwG18vgMgwWGq5AHP8DcdqRzBoEikQ50ESwhBNNVzkSoaSXVFum+MV25+yof3Lenzaih08JNZDwjHMljKCrmg5TipkU1BrnwKyXRGtrtHd+rPFwUhdoZ8Ni8gd7o7t5d/8BvrD25toBAAA= END Duration: 4.13 ms Memory Used: 57 MB Stream Logs 其實我們還可以讓我們的 Log 在 Terminal 串流，我會在 VS Code 開啟兩個 Terminal 來示範\n在 VS Code 打開一個 Terminal ( 快捷鍵: Ctrl + `)，然後輸入以下指令\n$ serverless logs -f hello --tail 我們再接著建立一個新的 Terminal，依照下圖所示按下 \u0026ldquo;+\u0026rdquo;\n配置一下 VS Code 版面，依照下圖拖曳 Terminal 到上面\n配置好版面，我們將使用下方的 Terminal 來調用 Lambda Function\n$ serverless invoke -f hello { \u0026#34;statusCode\u0026#34;: 200, \u0026#34;body\u0026#34;: \u0026#34;{\\\u0026#34;message\\\u0026#34;: \\\u0026#34;Go Serverless v3.0! Your function executed successfully!\\\u0026#34;, \\\u0026#34;input\\\u0026#34;: {}}\u0026#34; } 直接看下方的 gif 圖感受一下吧！ (這 gif 圖長達 27s 敬請耐心等待)\n透過今天的教學，我們學習到如何透過 Serverless Framework 來部署及開發 AWS Lambda，Serverless Framework 提供了一種高效且靈活的方式來運行和管理無服務器應用！\n參考資料 Serverless Framework - Setting Up Serverless Framework With AWS Serverless Framework - Providers GitHub - serverless/examples ","permalink":"https://shiun.me/blog/serverless-framework-101/","summary":"Serverless Framework 簡介 Serverless Framework 是一個開源的無服務器應用框架，它允許開發者快速建立、部署和管理在 AWS Lambda、Google Cloud Functions、Azure Functions 等雲平台上運行的無服務器應用。這個框架使用一個簡潔的配置文件（通常是 serverless.yml），在其中定義了應用的所有資源和設定，讓開發者可以專注於編寫業務邏輯而非管理基礎設施。\n安裝 Serverless Framework Prerequisites:\n需要有 npm，若你的電腦沒有 npm，請去下載 NodeJS $ npm install -g serverless 創建一個 Service $ serverless ? What do you want to make? AWS - Node.js - Starter AWS - Node.js - HTTP API AWS - Node.js - Scheduled Task AWS - Node.js - SQS Worker AWS - Node.js - Express API AWS - Node.js - Express API with DynamoDB AWS - Python - Starter \u0026gt; AWS - Python - HTTP API AWS - Python - Scheduled Task AWS - Python - SQS Worker AWS - Python - Flask API AWS - Python - Flask API with DynamoDB Other 輸入指令後透過方向鍵選取你要的 Template，本文將已 AWS - Python HTTP API 示範","title":"Serverless Framework 101 - 輕鬆開發並快速部署你的 AWS Lambda"},{"content":"在準備 SAA 的過程中，我覺得最難的部分就是網路，這篇文章主要先介紹一下 IGW 和 NAT Gateway 的差異，接著介紹一些使用心得。\n另外分享一下活動，最近我們大使推出了一個「證照陪跑計畫」，可以透過這個活動拿到 50% 折價券、AWS 贈品、考照學習資源以及加入 DC 社群，一直招募到 2024/04/28，有興趣的讀者可以來報名！(報名表單連結)\nInternet Gateway (IGW) IGW 是一種允許 VPC 與 Internet 之間通訊的 VPC 組件。它能讓 VPC 內的資源如 EC2 Instance 直接訪問 Internet，同時也能讓 Internet 上的使用者訪問 VPC 內的資源。\n主要功能包括：\n雙向通訊支持：允許配有 Public IP 的 Instance 訪問 Internet，同時也能接收來自 Internet 的數據。 高度的可靠性和擴展性：確保無需用戶干預即可維持服務的持續可用。 NAT Gateway NAT Gateway 是一種網路地址轉換服務，允許 Private Subnet 中的 Instance 連接到 VPC 外部的服務，同時阻止外部服務主動連接這些實例。這種設計特別適合需要訪問 Internet 但不需要從 Internet 接受直接訪問的敏感或保密環境。\nNAT Gateway 價錢:\nNAT Gateway 收費 = NAT Gateway 開啟時間 USD 0.062 per Hour + 經過 NAT 資料處理費用 USD 0.062 per GB + (Optional) 跨 AZ 傳輸費用 0.02 per GB (發送+接收) 總而言之很貴，最新價錢請參考 AWS 官方\n主要功能包括：\n單向連接：保護實例不被直接從互聯網訪問，同時允許它們安全地訪問必要的外部資源。 高效的地址轉換：將出站流量從 Private IP 地址轉換為 NAT Gateway 的公共IP地址。 如果你覺得還是很難懂 NAT 是什麼，我這邊就以一個學生宿舍做個比喻吧！如果你是學生，你現在住在學生宿舍的 0413 房，你不想給別人知道你的房號是幾號。\n你如果想要寄信給芬蘭的聖誕老公公，那你這時候會寫好信，收件地址是聖誕老公公的住址這無庸置疑，但是寄件人地址，你會以學校警衛室做為地址，你會透過警衛室的名義來寄出，所以當聖誕老公公收到信件時，他會看到寄信來的是學校警衛室寄出的；接著聖誕老公公要回信給你，他會寄到學生宿舍的警衛室收，警衛室會再把這封信轉傳給你，總而言之你寄出去的信，都會以學校警衛室的名義發出去 (至於警衛為什麼會知道進來的信要轉發給誰？關鍵字: NAT Translation table)\n核心差異與選擇考量 訪問權限：IGW 允許雙向通訊，適合需要與 Internet 雙向交互的 Public Subnet 應用，而 NAT Gateway 只支持出站訪問，適用於需要保護的 Private Subnet 環境。 實例需求：每個 VPC 僅需一個 IGW，而每個 AZ 可能需要一個 NAT Gateway 來保證服務的高可用性。 成本影響：使用 IGW 不會產生額外費用，而 NAT Gateway 則根據創建和使用情況收取費用。 一些使用心得 如果你的資源不怕別人看或是給別人直接訪問也沒什麼差，就別用 NAT Gateway 了，因為很貴！！！ Public IP 0.12/天，除非 Instances 達到一個數量 ，大約是 25 台 Instances，才會跟 2 個 NAT Gateway 打平，那時再來考慮改成 NAT Gateway 可能就會划算一點 最新價錢請參考 AWS 官方\n真的需要每個 AZ 都開 NAT Gateway 嗎？以 Tokyo 這 Region 來說，你有需要 3 個 AZ 都開嗎? 我自己是覺得要達到高可用性，兩個就夠了，不然真的很花錢，對於一些小專案或是 PoC，只要一個 NAT 然後背後的 Instance 共用同一個 NAT Gateway 即可 推薦文章: AWS NAT Gateway 佈局和設定\n","permalink":"https://shiun.me/blog/internet-gateway-vs-nat-gateway/","summary":"在準備 SAA 的過程中，我覺得最難的部分就是網路，這篇文章主要先介紹一下 IGW 和 NAT Gateway 的差異，接著介紹一些使用心得。\n另外分享一下活動，最近我們大使推出了一個「證照陪跑計畫」，可以透過這個活動拿到 50% 折價券、AWS 贈品、考照學習資源以及加入 DC 社群，一直招募到 2024/04/28，有興趣的讀者可以來報名！(報名表單連結)\nInternet Gateway (IGW) IGW 是一種允許 VPC 與 Internet 之間通訊的 VPC 組件。它能讓 VPC 內的資源如 EC2 Instance 直接訪問 Internet，同時也能讓 Internet 上的使用者訪問 VPC 內的資源。\n主要功能包括：\n雙向通訊支持：允許配有 Public IP 的 Instance 訪問 Internet，同時也能接收來自 Internet 的數據。 高度的可靠性和擴展性：確保無需用戶干預即可維持服務的持續可用。 NAT Gateway NAT Gateway 是一種網路地址轉換服務，允許 Private Subnet 中的 Instance 連接到 VPC 外部的服務，同時阻止外部服務主動連接這些實例。這種設計特別適合需要訪問 Internet 但不需要從 Internet 接受直接訪問的敏感或保密環境。\nNAT Gateway 價錢:\nNAT Gateway 收費 = NAT Gateway 開啟時間 USD 0.","title":"AWS Internet Gateway vs NAT Gateway 及使用心得分享"},{"content":"EventBridge 簡介 EventBridge Scheduler 是 AWS 在 2022/11 推出的新服務，相較於傳統事件驅動的 EventBridge，新推出的 Scheduler 是時間驅動的一個服務，你可以很輕易的在上面設置一些排程任務去調用 AWS 的其他服務，截至 2024/04/06，官方文件是顯示可以調用 AWS 超過 270 種服務，就我目前使用下來的心得，真的是相當易用！\n常見的使用場景:\n自動調整服務容量: 如 Amazon ECS 任務的數量或今天要介紹的 Aurora Serverless V2 ACU (今天要示範的) 自動化維護任務: 定時啟動或停止 EC2 Instance，以節省成本或進行系統維護。 SasS 訂閱即將到期通知: 蠻多 SaaS 系統可能會需要在用戶快到期時發送信件通知客戶續訂 架構說明 關於本文，我會預設讀者們對於 AWS 有基本的操作能力和認識，對於一些較瑣碎的動作會省略不講解\n本文要教學的是排程每天固定時間，會透過 EventBridge Scheduler 調用 Lambda Function 然後將 Aurora Serverless V2 的 ACU 降低。\n關於這個動作，我們其實要拆解成兩個部分:\n用 Lambda 去調整 Aurora Serverless v2 ACU 用 EventBridge 去 Trigger Lambda 下圖是從 AWS 官方 Blog 下載下來的架構圖，純示意圖大概讓各位認識 EventBridge 和 Lambda 的配合 整體的步驟大概會是\n創建 IAM Role 創建 Lambda Function 創建 EventBridge Scheduler 創建 IAM Policy 及 IAM Role 這個 IAM Role 將會給我們的 Lambda Function 可以去讀取和修改 Aurora\n創建 IAM Policy { \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;rds:DescribeDBClusters\u0026#34;, \u0026#34;rds:ModifyDBCluster\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;logs:CreateLogGroup\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:logs:ap-northeast-1:784523829721:*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;logs:CreateLogStream\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:logs:ap-northeast-1:784523829721:log-group:/aws/lambda/AdjustAuroraACU:*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: \u0026#34;logs:PutLogEvents\u0026#34;, \u0026#34;Resource\u0026#34;: \u0026#34;arn:aws:logs:ap-northeast-1:784523829721:log-group:/aws/lambda/AdjustAuroraACU:log-stream:*\u0026#34; } ] } 創建 IAM Role，然後 Attach 剛才所創建的 Policy 創建完畢應該會類似下圖 創建 Lambda Function Runtime: Python 3.12 IAM Role: {你剛才創建的 IAM Role} import boto3 def lambda_handler(event, context): # Extract ACU configuration and cluster identifier from the event min_capacity = event.get(\u0026#39;min_capacity\u0026#39;) max_capacity = event.get(\u0026#39;max_capacity\u0026#39;) cluster_identifier = event.get(\u0026#39;cluster_identifier\u0026#39;) # Ensure cluster_identifier is provided if not cluster_identifier: print(\u0026#34;Error: \u0026#39;cluster_identifier\u0026#39; not provided in the event.\u0026#34;) return { \u0026#39;statusCode\u0026#39;: 400, \u0026#39;body\u0026#39;: \u0026#34;\u0026#39;cluster_identifier\u0026#39; is required.\u0026#34; } # Modify the capacity for Aurora Serverless v2 modify_aurora_serverless_v2_capacity(cluster_identifier, min_capacity, max_capacity) def modify_aurora_serverless_v2_capacity(cluster_identifier, min_capacity, max_capacity): # Initialize the RDS client rds_client = boto3.client(\u0026#39;rds\u0026#39;) try: response = rds_client.modify_db_cluster( DBClusterIdentifier=cluster_identifier, ServerlessV2ScalingConfiguration={ \u0026#39;MinCapacity\u0026#39;: min_capacity, \u0026#39;MaxCapacity\u0026#39;: max_capacity }, ApplyImmediately=True ) print(f\u0026#34;Capacity update successful: {response}\u0026#34;) return { \u0026#39;statusCode\u0026#39;: 200, \u0026#39;body\u0026#39;: f\u0026#34;Capacity updated to Min: {min_capacity}, Max: {max_capacity} for {cluster_identifier}\u0026#34; } except Exception as e: print(f\u0026#34;Error updating capacity: {e}\u0026#34;) return { \u0026#39;statusCode\u0026#39;: 500, \u0026#39;body\u0026#39;: f\u0026#34;Error updating capacity for {cluster_identifier}: {e}\u0026#34; } 進到 Configuration - General Configuration 調整 Lambda Timeout，我這邊是改成 10s 創建 EventBridge Scheduler 搜尋 EventBridge 後進入該頁面，側邊選單點選 Scheduler/Schedules\n點擊 \u0026ldquo;Create Schedule\u0026rdquo;\n選擇 \u0026ldquo;Recurring schedule\u0026rdquo;\nTimezone: (UTC+8) Asia/Taipei Schedule Type: Cron-based schedule Cron Expression: 02 4 * * ? * (這邊請依照你想觸發的特定時間去更動) Next\nTarget Detail\nTarget API: Lambda Invoke: {選擇你剛才創建的 Lambda Function} Payload\n{ \u0026#34;cluster_identifier\u0026#34;: \u0026#34;demoAurora\u0026#34;, \u0026#34;min_capacity\u0026#34;: 1, \u0026#34;max_capacity\u0026#34;: 1.5 } Next\nPermisison: Create new role for this schedule\nCreate\n照著以上的步驟做到這裡就完成，照理來說應該就沒問題了\nTroubleshooting InvalidParameterCombination 這是我剛開始踩到的坑，原因是 Aurora Serverless V2 必須指定 ServerlessV2ScalingConfiguration\nYou can set the capacity of an Aurora DB instance with the ModifyDBCluster API operation. Specify the ServerlessV2ScalingConfiguration parameter.\nAWS Docs - What is Amazon EventBridge Scheduler?\n相關資料 AWS Docs - What is Amazon EventBridge Scheduler? AWS Docs - Managing Aurora Serverless v2 DB clusters ","permalink":"https://shiun.me/blog/how-to-schedule-aurora-serverless-v2-acu-adjustments-using-aws-lambda-and-eventbridge-scheduler/","summary":"EventBridge 簡介 EventBridge Scheduler 是 AWS 在 2022/11 推出的新服務，相較於傳統事件驅動的 EventBridge，新推出的 Scheduler 是時間驅動的一個服務，你可以很輕易的在上面設置一些排程任務去調用 AWS 的其他服務，截至 2024/04/06，官方文件是顯示可以調用 AWS 超過 270 種服務，就我目前使用下來的心得，真的是相當易用！\n常見的使用場景:\n自動調整服務容量: 如 Amazon ECS 任務的數量或今天要介紹的 Aurora Serverless V2 ACU (今天要示範的) 自動化維護任務: 定時啟動或停止 EC2 Instance，以節省成本或進行系統維護。 SasS 訂閱即將到期通知: 蠻多 SaaS 系統可能會需要在用戶快到期時發送信件通知客戶續訂 架構說明 關於本文，我會預設讀者們對於 AWS 有基本的操作能力和認識，對於一些較瑣碎的動作會省略不講解\n本文要教學的是排程每天固定時間，會透過 EventBridge Scheduler 調用 Lambda Function 然後將 Aurora Serverless V2 的 ACU 降低。\n關於這個動作，我們其實要拆解成兩個部分:\n用 Lambda 去調整 Aurora Serverless v2 ACU 用 EventBridge 去 Trigger Lambda 下圖是從 AWS 官方 Blog 下載下來的架構圖，純示意圖大概讓各位認識 EventBridge 和 Lambda 的配合 整體的步驟大概會是","title":"如何使用 AWS EventBridge Scheduler 及 Lambda 自動排程調整 AWS Aurora Serverless V2 ACU"},{"content":"前言 很多人在初學 Docker 時，通常都會知道 CMD 和 ENTRYPOINT 基本上可以互換，但又覺得很疑惑既然兩個指令能互換為什麼要提供兩個指令給我們用? 但其實這兩者是有一些差異的，今天這篇文章就是來帶你了解 Dockerfile 中的 CMD 和 ENTRYPOINT\nCMD vs ENTRYPOINT 我這邊準備了兩個 Dockerfile，分別使用了 CMD 和 ENTRYPOINT\nFROM ubuntu:22.04 CMD [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from CMD\u0026#34; ] FROM ubuntu:22.04 ENTRYPOINT [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from ENTRYPOINT\u0026#34; ] 接著 build 了兩個 docker image，分別取名為 docker-cmd, docker-entrypoint\n$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-entrypoint latest fa8ea026cc54 12 days ago 77.9MB docker-cmd latest bd73abaca1f4 12 days ago 77.9MB CMD 會被覆寫、ENTRYPOINT 則不會 理應來說我用 docker-cmd 這個 image 啟動一個容器，會打印出 Hello from CMD，執行結果確實也是如此:\n$ docker run docker-cmd Hello from CMD 但我現在在 docker run 後面加上額外的命令\n例如我額外加上: echo \u0026ldquo;I am shiun\u0026rdquo; 後面額外多加的指令會直接覆蓋掉原本的 CMD 指令，CMD 中的命令就不會被執行: $ docker run docker-cmd echo \u0026#34;I am shiun\u0026#34; I am shiun 同樣在 docker run 後面附加額外命令，如果是 ENTRYPOINT，則不會被覆寫，但是會傳遞給 ENTRYPOINT 作為額外參數\n例如我額外加上: echo \u0026ldquo;I am shiun\u0026rdquo; 這邊額外加上的命令，會被作為參數傳遞給 ENTRYPOINT 命令 注意一下！在這邊的上下文，我所指的「參數」是傳達給命令的額外資訊，用於影響該命令的行為。 參數不一定是選項 （像是 -f 或 -p），它們也可以是其他命令或文本值\n$ docker run docker-entrypoint echo \u0026#34;I am shiun\u0026#34; Hello from ENTRYPOINT echo I am shiun 補充說明: 有注意到引號(\u0026quot;) 不見了嗎? 引號的目的是確保被包含的字符串作為一個整體被傳遞，而不是被看作多個由空格分隔的獨立參數，就像我們在 Python print(\u0026quot;I am shiun\u0026quot;) ， \u0026ldquo;I am shiun\u0026rdquo; 整句被雙引號包裹住，整句會被視為「一個」字串\n由上面的執行結果會看到，我們傳入了兩個參數到 ENTRYPOINT\necho I am shiun 因此 ENTRYPOINT 最後執行的指令其實是:\n$ echo Hello from ENTRYPOINT echo I am shiun 如果兩個同時使用， CMD 會被作為 ENTRYPOINT 的默認參數 這次我再創建一個新的 Dockerfile，我會在該 Dockerfile 內同時使用 CMD 和 ENTRYPOINT\nFROM ubuntu:22.04 CMD [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from CMD\u0026#34; ] # CMD 和 ENTRYPOINT 誰前誰後不影響等下的結果 ENTRYPOINT [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from ENTRYPOINT\u0026#34; ] # CMD 和 ENTRYPOINT 誰前誰後不影響等下的結果 然後我 build 了 Image，取名為 docker-cmd-entrypoint\n馬上來 run 一個容器看看結果:\n$ docker run docker-cmd-entrypoint Hello from ENTRYPOINT echo Hello from CMD 會發現 CMD 原本要執行的指令被當作參數傳遞給 ENTRYPOINT\n要注意的是！！！ CMD 是被當成默認參數傳遞給 ENTRYPOINT 注意！！！是默認(預設)，也就是如果你沒特別指定，那我就默認使用這個值的概念\n讓我們來看一下官方文件的說明:\nCommand line arguments to docker run will be appended after all elements in an exec form ENTRYPOINT, and will override all elements specified using CMD.\n官方文件: https://docs.docker.com/engine/reference/builder/#entrypoint\n因此 ENTRYPOINT 那邊最後實際執行的指令是:\n$ echo Hello from ENTRYPOINT echo Hello from CMD 同時使用 CMD 和 ENTRYPOINT，在 docker run 指令又加上額外的命令 這邊的情境一樣使用到 docker-cmd-entrypoint 這個 Image 但我這次執行 docker run 會在後方附上額外的命令\n完整的指令會長這樣: docker run docker-cmd-entrypoint echo \u0026quot;I am shiun\u0026quot;\n這邊可以先停下來思考一下，回顧前面，特別是官方說明的部分，猜猜看上面的指令執行結果會是如何\nCommand line arguments to docker run will be appended after all elements in an exec form ENTRYPOINT, and will override all elements specified using CMD.\n官方文件: https://docs.docker.com/engine/reference/builder/#entrypoint\n執行結果如下:\n$ docker run docker-cmd-entrypoint echo \u0026#34;I am shiun\u0026#34; Hello from ENTRYPOINT echo I am shiun 由此可知，docker run 後面所附加的額外命令，會覆蓋掉 CMD 的內容，而且會作為參數傳遞至 ENTRYPOINT\n總結 ENTRYPOINT 容器啟動時必須執行的命令 CMD 容器啟動時默認(預設)執行的命令 ENTRYPOINT, CMD 同時使用 CMD 的內容變成 ENTRYPOINT 默認參數 docker run 後面若有附加命令 會覆蓋掉 CMD 的內容，而且會作為參數傳遞至 ENTRYPOINT ","permalink":"https://shiun.me/blog/dockerfile-cmd-vs-entrypoint/","summary":"前言 很多人在初學 Docker 時，通常都會知道 CMD 和 ENTRYPOINT 基本上可以互換，但又覺得很疑惑既然兩個指令能互換為什麼要提供兩個指令給我們用? 但其實這兩者是有一些差異的，今天這篇文章就是來帶你了解 Dockerfile 中的 CMD 和 ENTRYPOINT\nCMD vs ENTRYPOINT 我這邊準備了兩個 Dockerfile，分別使用了 CMD 和 ENTRYPOINT\nFROM ubuntu:22.04 CMD [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from CMD\u0026#34; ] FROM ubuntu:22.04 ENTRYPOINT [ \u0026#34;echo\u0026#34;, \u0026#34;Hello from ENTRYPOINT\u0026#34; ] 接著 build 了兩個 docker image，分別取名為 docker-cmd, docker-entrypoint\n$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-entrypoint latest fa8ea026cc54 12 days ago 77.9MB docker-cmd latest bd73abaca1f4 12 days ago 77.9MB CMD 會被覆寫、ENTRYPOINT 則不會 理應來說我用 docker-cmd 這個 image 啟動一個容器，會打印出 Hello from CMD，執行結果確實也是如此:","title":"Docker 初學常見問題 - CMD vs ENTRYPOINT 兩者的差異與範例"},{"content":"我踩到了什麼坑 本文的背景延續自我之前的文章《一個專案需要多個 Dockerfile - 淺談建構上下文 (build context)》\n因為我們目前經手的專案會需要針對不同環境或是測試 Build 不同的 Image，為了讓目錄架構更具組織性且容易理解，我們根據不同的環境把各個環境的 Dockerfile, docker-compose.yaml, .sh 等等放在各個環境的目錄下，現在的目錄架構大概像這樣 ↓：\nE2Eproject/ ├── testenvironment1/ │ └── Dockerfile ├── testenvironment2/ │ ├── docker-compose.yaml │ └── Dockerfile ├── start-headless-tests.sh └── requirements.txt 由於我正在本地 Debug 這個專案，需要在本地運行 Docker-compose，我便很自然的執行 docker-compose -f ./cicd/headless/docker-compose.yaml up，結果 Docker 容器運行起來時，遇了一個錯誤：/bin/bash: /usr/src/app/cicd/headless/start-headless-tests.sh: No such file or directory。\n這個錯誤讓我想也想不透哪邊出錯，我一開始都是針對 Dockerfile 去做動作，但完全沒有用\n這邊就附上當時錯誤發生時的 Dockerfile 和 docker-compose.yaml，大家可以試著猜猜看是哪個環節導致錯誤！\nE2Eproject/cicd/headless/Dockerfile ↓\n# Dockerfile # Use the official Python base image from the DockerHub. FROM python:3.12 # Set the working directory within the container. WORKDIR /usr/src/app # 略..... # 略..... # 略..... # Copy all files from the current directory on the host to the working directory in the container. # This includes the application source code and any additional required files. COPY . /usr/src/app/ # Copy the start-tests.sh script into the container\u0026#39;s work directory. RUN chmod +x /usr/src/app/start-headless-tests.sh # The command to run the application. CMD [\u0026#34;/bin/bash\u0026#34;, \u0026#34;/usr/src/app/cicd/headless/start-headless-tests.sh\u0026#34;] E2Eproject/cicd/headless/docker-compose.yaml ↓\n# docker-compose.yaml version: \u0026#39;3\u0026#39; services: selenium-hub: # 略 ... chrome-node: # 略 ... E2Eproject: container_name: E2Eproject build: context: ../../ dockerfile: ./cicd/headless/Dockerfile args: NO_CACHE: ${NO_CACHE:-false} depends_on: - selenium-hub networks: - network-grid volumes: - .:/usr/src/app networks: network-grid: Docker Volume 介紹 (Bind Mount vs Volume) 圖片取自：https://docs.docker.com/storage/volumes/\n要解掉這個坑，會牽扯到 Docker Volume 的概念，但本篇踩坑紀錄不會做太深入的講解，就簡單介紹一下\n在 Docker 中，Volume 是用來持久化和共享數據的重要機制。大致上，我們可以將其分為兩類：Bind Mount 和 Volume。今天，我們不打算討論第三種類型，tmpfs mount，因為它與今天的話題不太相關。\nBind Mount 使用時機 Bind Mount 是一種將宿主機(Host)的文件或目錄掛載到容器中的方法。適合以下情況：\n開發階段：當你需要對程式碼進行快速迭代時，使用 Bind Mount 可以即時反映宿主機上的更改。 這也是為何我 Debug 時，使用 Bind Mount 的原因，因為我在 Host 上的改動可以立即顯現出來 但這不意味著 Bind Mount 不適合用在 Production 環境，只是 Bind Mount 會依賴宿主機(Host)目錄系統的結構，在安全和一致性上讓你更難處理 日誌文件的處理：將日誌文件直接掛載到宿主機，方便進行日誌的收集和分析。 Volume 使用時機 Volume 則是由 Docker 管理的一種更加隔離和安全的數據持久化方法，官方也推薦使用 Volumes，我這邊就節錄一小段 Docker 官方列舉的優點，詳細可以去看一下官方文件(連結)：\nVolumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts:\nVolumes are easier to back up or migrate than bind mounts. Volumes work on both Linux and Windows containers. You can manage volumes using Docker CLI commands or the Docker API. Volumes can be more safely shared among multiple containers. Volume drivers let you store volumes on remote hosts or cloud p -roviders, encrypt the contents of volumes, or add other functionality. New volumes can have their content pre-populated by a container. Volumes on Docker Desktop have much higher performance than bind mounts from Mac and Windows hosts. 然後我也列舉一些 Volume 適合的情境：\n生產環境：在生產環境中，我們更關注數據的持久化和安全，Volume 提供了更好的隔離。 不希望與宿主機的文件系統直接交互：當需要對數據進行持久化存儲，並且不希望與宿主機的文件系統直接交互時。 相比 Bind Mount，Volume 就不用擔心宿主機因為不同作業系統表示路徑的方式不太一樣，因為 Volume 由 Docker 完全管理，例如： Windows: C:/Users/shiun/Documents/my_folder Mac: C:/Users/shiun/Documents/my_folder Linux: /home/shiun/my_folder 為什麼會發生錯誤 首先一定要了解在 docker-compose.yaml 中，Bind Mount 在指定宿主機的目錄路徑時，路徑的相對路徑是基於 docker-compose.yaml 的所在目錄:\n# 略... volumes: - .:/usr/src/app # \u0026lt;docker-compose.yaml 所處當前目錄路徑\u0026gt;: \u0026lt;容器目標目錄路徑\u0026gt; 再回到我遇到的問題，原因其實很簡單：\n在我使用的 Dockerfile 中，我使用了 COPY . /usr/src/app/ 指令將文件從建構上下文中複製到容器內。 但是，當容器啟動時，docker-compose.yaml 中定義的 volume 又將我本地的 E2Eproject/cicd/headless 目錄掛載到了同一位置。 這導致了容器中的 /usr/src/app 目錄內容被覆蓋，而且 start-headless-tests.sh 腳本並不存在於 E2Eproject/cicd/headless 中，因此容器找不到這個文件，進而出現 /bin/bash: /usr/src/app/cicd/headless/start-headless-tests.sh: No such file or directory 解決方法 解決這個問題其實很簡單：\n註解掉或移除 docker-compose.yaml 的 volume 指令，每一次有 code 改動都重新 Build Image 這樣就不會有掛載覆蓋的問題，畢竟我再 Dockerfile 裡面就有把整個專案目錄 COPY 到 WORKDIR 修改 volumes 設定：將它改為 ../../，這樣就會掛載 E2Eproject 目錄，而不是僅僅掛載 E2Eproject/cicd/headless。 再次提醒，這邊的相對路徑是基於 docker-compose.yaml 的所在目錄 改好的樣子會像這樣：\nE2Eproject/cicd/headless/docker-compose.yaml ↓\n# docker-compose.yaml version: \u0026#39;3\u0026#39; services: # 略... # 略... # 略... E2Eproject: container_name: E2Eproject build: context: ../../ dockerfile: ./cicd/headless/Dockerfile args: NO_CACHE: ${NO_CACHE:-false} depends_on: - selenium-hub networks: - network-grid volumes: - ../../:/usr/src/app # 關鍵修改的地方 networks: network-grid: 當然後續我還有做一些簡單的細節調整，但就不多加贅述，也不影響本篇踩坑紀錄的內容\n若文章內容有誤，歡迎隨時連絡我！你們的回饋對我來說相當重要！\n也歡迎跟我交流或是分享你的想法\n","permalink":"https://shiun.me/blog/docker-overwriting-workdir-contents-with-bind-mounts-at-run-time/","summary":"我踩到了什麼坑 本文的背景延續自我之前的文章《一個專案需要多個 Dockerfile - 淺談建構上下文 (build context)》\n因為我們目前經手的專案會需要針對不同環境或是測試 Build 不同的 Image，為了讓目錄架構更具組織性且容易理解，我們根據不同的環境把各個環境的 Dockerfile, docker-compose.yaml, .sh 等等放在各個環境的目錄下，現在的目錄架構大概像這樣 ↓：\nE2Eproject/ ├── testenvironment1/ │ └── Dockerfile ├── testenvironment2/ │ ├── docker-compose.yaml │ └── Dockerfile ├── start-headless-tests.sh └── requirements.txt 由於我正在本地 Debug 這個專案，需要在本地運行 Docker-compose，我便很自然的執行 docker-compose -f ./cicd/headless/docker-compose.yaml up，結果 Docker 容器運行起來時，遇了一個錯誤：/bin/bash: /usr/src/app/cicd/headless/start-headless-tests.sh: No such file or directory。\n這個錯誤讓我想也想不透哪邊出錯，我一開始都是針對 Dockerfile 去做動作，但完全沒有用\n這邊就附上當時錯誤發生時的 Dockerfile 和 docker-compose.yaml，大家可以試著猜猜看是哪個環節導致錯誤！\nE2Eproject/cicd/headless/Dockerfile ↓\n# Dockerfile # Use the official Python base image from the DockerHub.","title":"Docker 踩坑紀錄 - 運行階段 Bind Mount 覆蓋 WORKDIR 的內容"},{"content":"報名流程 履歷及申請動機 本篇文章主要以「技術支援」這個角度來探討履歷準備的方向，當然！這當中肯定有很多方向也是其他職能也可以拿來參考用的。即便您第一志願不是「技術支援」職能，我仍然建議你繼續讀完！\n先來了解 Technical Support (技術支援)這個角色會做哪些事 技術工作坊上台演講並教學雲端技術 提供組內雲端技術的諮詢和建議 撰寫工作坊所需的技術教學文件及應用 履歷準備方向 了解技術支援這個角色會做哪些事，盡可能地在撰寫履歷時，使內容可以讓面試官覺得你很適合這個角色\n教學技術的經驗 上台演講的經驗 技術專案的經驗 有使用到 AWS 尤佳 內容可以和 Amazon 領導力準則掛勾 展現你在技術上的所專精的專業領域，例如：\nAI Data Analysis DevOps \u0026hellip; 展現你的 Leadership 和 Ownership\n量化你的成果\n雲端技術證照\n履歷常見錯誤 ❌ 履歷的大頭照可放可不放，但仍建議不要放，部分職位、傳統台商或特別要求則例外，原因如下： 避免因種族、膚色、人種\u0026hellip; 產生任何 Bias 篇幅過長，超過兩頁 技術方面寫的過多過雜、許多不必要的雜訊，例如： 您有提到您會 Flask, Django，那就不必列出你會 Python 把有碰過的技術都寫上去，例如： 僅用過 Java 印出過 \u0026ldquo;Hello World\u0026rdquo; 就在「技能」區塊寫 Java 把專精的技術寫上去就好，怎樣算是專精呢？只要您對於這個技術有信心給面試官隨便問 5 個 Why 你都有信心可以回答就寫上來 未量化你的各經歷的成果，例如： 你曾舉辦過技術工作坊，但你僅僅寫了你任期內舉辦了工作坊 更好的寫法是，你可以告訴我們你舉辦了幾人的 XXX 技術工作坊，觀眾對於您的講評評價高達 4.8/5 分 把自己描述成跨領域通才了，你應要把自己描述成一個專精 XX 技術領域的跨領域人才，例如： 因履歷投遞的職能是「技術支援」，你應該要讓自己成為一個「專精 XX 技術領域的雲端人才，同時附帶了活動規劃的跨領域優勢」 而非把自己寫的什麼都很會，讓人摸不清你到底是來投技術職能還是來投活動規畫職能，又或者讓人覺得你是一個通才 把其他人的貢獻寫在自己的履歷，例如： 你參與了某專案你僅負責後端開發，前端開發部分並非你做的，然而你卻在專案中的經歷寫到你使用了前端的 XXX 技術做了 XXX 功能 申請動機準備方向 申請動機這一塊就有比較多自由發揮的空間 以下就給出兩點簡單的小建議：\n展現您對於雲端技術的學習意願 有建設性的任期規劃 履歷審錄取，進到面試 履歷審查錄取後，就會進去面試這一大關，面試這一大關又分成兩小關\n團體面試 個人面試 面試不開放線上面試！\n當時我通過履歷審，收到的面試通知信件 ↓ 面試服裝 面試服裝沒有強制規定，建議上網搜尋 Smart Casual 穿搭！\n(當然並不是說沒有強制規定就可以穿夾腳拖短褲背心來)\n團體面試 以下題目部分僅供參考，並非每屆的形式、題目都會一模一樣！\n10 人一組，一組裡面一定會有「活動規劃」、「行銷規廣」、「技術支援」的人 一開始先輪流每人 1 分鐘的自我介紹 會有一個面試官在前面說題目，題目通常都是要準備一場活動，然後要我們這一組合作，在時限內從一開始的活動規劃，一直到最後宣傳圖文產出，最後把活動整體的內容和圖文報告給面試官聽 過程中，場邊會有多位面試官，審視大家在團隊中的表現 在尾聲，會再讓每個人輪流發言一次，要告訴面試官在剛剛的過程中自己扮演什麼角色，在團隊中有做出什麼貢獻，一樣限時 1 分鐘 團體面試的一些建議 在一開始時，大家一定都不認識彼此，要開始團隊討論時，往往需要有一個人來破冰，第一個說話的人確實會比較容易令人印象深刻 避免一直搶 Spotlight，想刻意讓大家關注到你，並非在團隊中講越多話就越能受到賞識，話過多但卻沒帶來實質效應可能會帶來反效果 事前訓練自己的表達能力與溝通能力，因為身為技術的你會發現當你要跨團隊與「活動規劃」、「行銷推廣」的組員溝通時，你用太專業的術語對方會聽不懂 個人面試 以下題目部分僅供參考，並非每屆的形式、題目都會一模一樣！\n在履歷審通過後，會收到一封錄取信，通常會在錄取信裡面提及個人面試的題目 題目會給你 AWS 的幾個服務，你挑一個服務，並且在當天給你 8 分鐘介紹 演講形式不拘，所以你要自備紙本講義也沒問題，但我看大多數人都是做 PPT 來介紹 會被安排到一個會議室，裡面有電視可以接你的電腦 (會議室內有 HDMI 線) 面試官會有兩位，以我當時報名 5th 為例：一個是 AWS 的正職、一個是 AWS 雲端大使 Team Lead 8 分鐘時間把服務講解完後，面試官會對你做一些 Q\u0026amp;A，通常會針對你的履歷和剛剛演講的內容來發問 個人面試的一些建議 (重要) 身為技術職能的雲端大使，因為我們在舉辦很多活動時，面對的聽眾很多都沒有專業雲端知識背景，或是雲端小白，如何把一個技術內容講的讓雲端小白都聽得懂的能力相當重要！ 你可以舉一些生活中的真實情境，例如： 我當時講解 DynamoDB ，我用「登入驗證」的這個真實情境，講解 DynamoDB 在這過程中發揮的作用 找一些身邊的朋友，最好找沒有雲端技術背景的人，講解一次給他聽，並汲取對方的回饋來改進 不要引用到一些太難的觀念或是內容 履歷真的不要亂寫，被問到一問三不知就尷尬了 了解 Amazon 領導力準則，在 Q\u0026amp;A 時，讓自己的故事可以跟領導力準則切合 描述自己的故事時善用 STAR 原則 常見問題 我已經把履歷投出去了，現在才看到這篇文章想要重新修改履歷怎麼辦? 直接重新投遞一次就好喔！再次提醒，記得履歷連結要設定成「公開」 報名連結: https://www.surveycake.com/s/DZk3O 面試當天有事，有辦法線上面試嗎? 不行，僅開放實體面試 面試是全英文嗎? 需要準備英文自我介紹嗎? 面試全程皆以中文為主 履歷有規定要用英文嗎? 中文英文皆可！ 結語 希望這篇文章可以幫助到每位想來參加 AWS Educate 雲端大使的人，在這過程中可以積累人脈，體驗外商文化，同時在雲端大使任內也會有其他合作專案像是 DGR, MKT，讓你真的參與 AWS 正職的真實工作環境中！\n6th 雲端大使報名連結: https://www.surveycake.com/s/DZk3O Instagram: https://www.instagram.com/awseducatestdambtw/ Facebook: https://www.facebook.com/awseducatestudentambassadortaiwan/ ","permalink":"https://shiun.me/blog/aws-educate-6th-cloud-ambassador-resume-and-interview-preparation/","summary":"報名流程 履歷及申請動機 本篇文章主要以「技術支援」這個角度來探討履歷準備的方向，當然！這當中肯定有很多方向也是其他職能也可以拿來參考用的。即便您第一志願不是「技術支援」職能，我仍然建議你繼續讀完！\n先來了解 Technical Support (技術支援)這個角色會做哪些事 技術工作坊上台演講並教學雲端技術 提供組內雲端技術的諮詢和建議 撰寫工作坊所需的技術教學文件及應用 履歷準備方向 了解技術支援這個角色會做哪些事，盡可能地在撰寫履歷時，使內容可以讓面試官覺得你很適合這個角色\n教學技術的經驗 上台演講的經驗 技術專案的經驗 有使用到 AWS 尤佳 內容可以和 Amazon 領導力準則掛勾 展現你在技術上的所專精的專業領域，例如：\nAI Data Analysis DevOps \u0026hellip; 展現你的 Leadership 和 Ownership\n量化你的成果\n雲端技術證照\n履歷常見錯誤 ❌ 履歷的大頭照可放可不放，但仍建議不要放，部分職位、傳統台商或特別要求則例外，原因如下： 避免因種族、膚色、人種\u0026hellip; 產生任何 Bias 篇幅過長，超過兩頁 技術方面寫的過多過雜、許多不必要的雜訊，例如： 您有提到您會 Flask, Django，那就不必列出你會 Python 把有碰過的技術都寫上去，例如： 僅用過 Java 印出過 \u0026ldquo;Hello World\u0026rdquo; 就在「技能」區塊寫 Java 把專精的技術寫上去就好，怎樣算是專精呢？只要您對於這個技術有信心給面試官隨便問 5 個 Why 你都有信心可以回答就寫上來 未量化你的各經歷的成果，例如： 你曾舉辦過技術工作坊，但你僅僅寫了你任期內舉辦了工作坊 更好的寫法是，你可以告訴我們你舉辦了幾人的 XXX 技術工作坊，觀眾對於您的講評評價高達 4.8/5 分 把自己描述成跨領域通才了，你應要把自己描述成一個專精 XX 技術領域的跨領域人才，例如： 因履歷投遞的職能是「技術支援」，你應該要讓自己成為一個「專精 XX 技術領域的雲端人才，同時附帶了活動規劃的跨領域優勢」 而非把自己寫的什麼都很會，讓人摸不清你到底是來投技術職能還是來投活動規畫職能，又或者讓人覺得你是一個通才 把其他人的貢獻寫在自己的履歷，例如： 你參與了某專案你僅負責後端開發，前端開發部分並非你做的，然而你卻在專案中的經歷寫到你使用了前端的 XXX 技術做了 XXX 功能 申請動機準備方向 申請動機這一塊就有比較多自由發揮的空間 以下就給出兩點簡單的小建議：","title":"如何成為 AWS Educate 雲端大使？履歷準備、面試技巧大公開"},{"content":"最近在寫 E2E 測試遇到一個問題，因 E2E 專案中，除了專案本身的 Docker Image 需要 Build 之外，還有多個測試環境的 Image 也要 Build，這造成了我在這個專案上需要創建多個 Dockerfile\n發生了什麼問題? 我一開始的錯誤處理方式 菜鳥時期的我，以為 Dockerfile 就是一定得命名為\u0026quot;Dockerfile\u0026quot;，這導致了我沒辦法在專案根目錄下創建三個 Dockerfile，因為會導致命名衝突\n那我想出了什麼處理方式？相當簡單，很菜的我，一開始便自然地根據不同環境在專案下創建了不同的目錄，然後在目錄底下存放各自的 Dockerfile 就很類似這種感覺：\nE2Eproject/ ├── testenvironment1/ │ └── Dockerfile ├── testenvironment2/ │ └── Dockerfile ├── requirements.txt └── Dockerfile 接著便接著發生下一個問題 — 錯誤的建構上下文 以其中一個測試環境內的 Dockerfile 為範例，當時我的寫法如下，請特別注意 COPY ../ /usr/src/app/ 這行\n# Use the official Python base image from the DockerHub. FROM python:3.12 # Set the working directory within the container. WORKDIR /usr/src/app # Set the PYTHONPATH environment variable. This is where Python looks for modules. # It\u0026#39;s set to the work directory to allow local modules to be found. ENV PYTHONPATH /usr/src/app # Copy the contents of the current host directory into the container\u0026#39;s work directory. COPY ../ /usr/src/app/ # Install the project dependencies specified in the requirements.txt file. # The --no-cache-dir option is used to disable the cache and reduce the layer size. RUN pip install --no-cache-dir -r ../requirements.txt # 略 ... RUN chmod +x /usr/src/app/start-tests.sh # The command to run the application. CMD [\u0026#34;sh\u0026#34;, \u0026#34;/usr/src/app/start-headless-tests.sh\u0026#34;] 注意：本文的 Dockerfile 例子主要用於展示建構上下文的概念。在實際開發中，建議採用層級快取（layer caching）的最佳實踐，也就是先複製並安裝 requirements.txt，再複製其他檔案。有關 Build cache 的資訊請查閱 Docker 官方文檔。\n會注意到，我使用了 ../，會這麼寫是因為專案根目錄(或是說打包所需的檔案)都在此 Dockerfile 所處目錄的上一層 接著就是很自然的輸入下方指令，然後就出現 ERROR 了：\n$ cd testenvironment1 $ docker build -t e2e-project-testenv:v1 . Dockerfile:16 -------------------- 14 | # Install the project dependencies specified in the requirements.txt file. 15 | # The --no-cache-dir option is used to disable the cache and reduce the layer size. 16 | \u0026gt;\u0026gt;\u0026gt; RUN pip install --no-cache-dir -r ../requirements.txt 17 | 18 | # Install additional dependencies for HTML report generation -------------------- ERROR: failed to solve: process \u0026#34;/bin/sh -c pip install --no-cache-dir -r ../requirements.txt\u0026#34; did not complete successfully: exit code: 1 Service \u0026#39;E2E-project\u0026#39; failed to build : Build failed TL;DR Docker 建構上下文就是告訴 Docker 從哪個目錄開始打包檔案，例如：你在執行指令 docker build . 這個 \u0026ldquo;.\u0026rdquo; 就是建構上下文，Docker 會將這個目錄及其子目錄下的所有檔案作為建構上下文打包成 tar 檔案\nDockerfile 與 build image 的上下文目錄不必強關聯在一起\n我們在指令中指定一個目錄作為上下文，然後也透過 -f 參數指定使用哪個建構檔案，並且名稱可以自己任意命名\ndocker build -t e2e-project-testenv:v1 -f testenvironment1/Dockerfile /myapp 先來了解什麼是 Docker 建構上下文 推薦文章：深入理解 docker build 中的建構上下文\n什麼是 Docker 建構上下文(build context) 首先，讓我們簡單回顧一下 Docker 的基本概念。Docker 允許您打包應用程式和所需環境到一個稱為 \u0026ldquo;鏡像\u0026rdquo;(image) 的容器中。這個鏡像可以在任何安裝了 Docker 的系統上運行。\nDocker 建構上下文的概念：\n當您使用 docker build 命令建立 Docker 鏡像時，Docker 會將指定路徑下的檔案和目錄打包成一個 tar 檔案。 這個 tar 檔案被稱為 \u0026ldquo;建構上下文\u0026rdquo;(build context)。 為什麼需要建構上下文：\nDocker 的鏡像是在 Docker 伺服器（通常是遠端伺服器）上建構的。 為了建構鏡像，Docker 伺服器需要訪問到所有必要的檔案，比如原始碼、配置檔案等。 因此，客戶端（您的電腦）會把這些檔案打包成 tar 檔案，然後上傳給伺服器。 Dockerfile 和建構上下文：\nDockerfile 是一個包含了建構鏡像所需步驟的文本檔案。 在 Dockerfile 中，您可以引用建構上下文中的檔案。比如，您可以複製建構上下文中的檔案到鏡像裡，或者執行建構上下文中的腳本。 總結來說，Docker 建構上下文是 Docker 客戶端將建構鏡像所需的檔案打包並傳輸給 Docker 伺服器的過程。這確保了 Docker 伺服器有所有必要的檔案來建構鏡像。\n為何我一開始的處理方式會出現 ERROR? 關鍵就出在：\n當我們執行 docker build -t e2e-project-testenv:v1 .，Docker 客戶端會先將後面的指定路徑(.) 打包成一個 tar 檔案，傳送給 Docker 伺服器端，接著才會根據 Dockerfile 中所定義的腳本進行構建 什麼意思呢？\n我當時執行指令 cd testenvironment1 接著執行 docker build -t e2e-project-testenv:v1 . 事實上就是把 testenvironment1 這個目錄以及其子目錄下的所有檔案打包好，傳送至 Docker Daemon Docker 在 Build 的時候只能取用上下文的檔案，requirements.txt 位於這個目錄的上層(即 E2Eproject 目錄中)，因此它不會被包含在建構上下文中，也就無法在 Dockerfile 中被訪問。 更優雅的處理方式 如果建構鏡像時沒有明確指定 Dockerfile，那麼 Docker Client 默認在建構鏡像時指定的上下文路徑下找名字為 Dockerfile 的建構檔案\n但事實上，Dockerfile 與 build image 的上下文目錄不必強關聯在一起 我們在指令中指定一個目錄作為上下文 然後也透過 -f 參數指定使用哪個建構檔案 並且名稱可以自己任意命名！ 並且名稱可以自己任意命名！！ 並且名稱可以自己任意命名！！！ 這完完全全解惑了我當初菜鳥所以為的「 Dockerfile 就是一定得命名為\u0026quot;Dockerfile\u0026quot;」\n例如：\n$ cd E2Eproject $ docker build -t e2e-project-testenv:v1 -f testenvironment1/Dockerfile . 參考資料 Docker-学习系列25-Dockerfile-中的-COPY-与-ADD-命令.html\n深入理解 docker build 中的建構上下文\n","permalink":"https://shiun.me/blog/a-project-with-multiple-dockerfiles-an-introduction-to-build-context/","summary":"最近在寫 E2E 測試遇到一個問題，因 E2E 專案中，除了專案本身的 Docker Image 需要 Build 之外，還有多個測試環境的 Image 也要 Build，這造成了我在這個專案上需要創建多個 Dockerfile\n發生了什麼問題? 我一開始的錯誤處理方式 菜鳥時期的我，以為 Dockerfile 就是一定得命名為\u0026quot;Dockerfile\u0026quot;，這導致了我沒辦法在專案根目錄下創建三個 Dockerfile，因為會導致命名衝突\n那我想出了什麼處理方式？相當簡單，很菜的我，一開始便自然地根據不同環境在專案下創建了不同的目錄，然後在目錄底下存放各自的 Dockerfile 就很類似這種感覺：\nE2Eproject/ ├── testenvironment1/ │ └── Dockerfile ├── testenvironment2/ │ └── Dockerfile ├── requirements.txt └── Dockerfile 接著便接著發生下一個問題 — 錯誤的建構上下文 以其中一個測試環境內的 Dockerfile 為範例，當時我的寫法如下，請特別注意 COPY ../ /usr/src/app/ 這行\n# Use the official Python base image from the DockerHub. FROM python:3.12 # Set the working directory within the container. WORKDIR /usr/src/app # Set the PYTHONPATH environment variable.","title":"一個專案需要多個 Dockerfile - 淺談建構上下文 (build context)"},{"content":"大家好我是 Shiun，這篇是我的個人網站的第一篇文章，會記錄我的動機以及現在的時空背景，以此來記錄一下自己的個人成長，未來回頭來看看自己成長多少\n架設網站的動機 動機與原因 會想要架設個人網站主要有以下原因：\n想要記錄自己的個人成長，以及隨手筆記 受到 Nic 的啟發 (Youtube連結)，覺得對於未來職涯的道路上會有所幫助，能夠建立個人品牌，彰顯專業 自己本身就很熱愛分享知識、技術，而且很享受上台演講的氛圍 看起來很帥 2024 年 1 月，下定決心 一直以來都想架一個自己的個人網站，但遲遲一直沒有下手，絕大部分是因為懶 XD，而我一直以來都會把自己的想法紀錄在 Google Keep 或是 Dropbox Paper\n直到 2024/01 下定決心今年的新希望(目標)一定要架好個人網站，開始寫文章，而且我今年(2024)也準備要畢業了，即將面對職場，這時候再不趕快產出有質量的內容就太虧了\n關於我 Hi 我是 Shiun，家裡有兩隻貓(娜娜 \u0026amp; 妮妮😺😺) 我目前就讀於某私立大學的資管系(2024 畢業)，同時也就職於：\nAWS Educate 5th Cloud Ambassador - Technical Support @AWS Backend Engineer Intern @eGroupAI 在 2024 年 3 月要接任：\nAWS Educate 6th Cloud Ambassador Team Lead (技術) @AWS Cloud Engineer Intern @eCloudValley 主要專精於後端與雲端及 DevOps 方面的技術：\nSpring boot Jenkins AWS Docker Selenium Pytest 除此之外，我也很擅長擔任領導者的角色，在求學職涯中，我幾乎都是擔任領導者的職位，像是在大學畢業專題中我也擔任了組長、PM、Product Owner、Backend Development，在畢業專題(作品連結)中上實踐了敏捷式開發(Scrum, Kanban)\n","permalink":"https://shiun.me/blog/my-first/","summary":"大家好我是 Shiun，這篇是我的個人網站的第一篇文章，會記錄我的動機以及現在的時空背景，以此來記錄一下自己的個人成長，未來回頭來看看自己成長多少\n架設網站的動機 動機與原因 會想要架設個人網站主要有以下原因：\n想要記錄自己的個人成長，以及隨手筆記 受到 Nic 的啟發 (Youtube連結)，覺得對於未來職涯的道路上會有所幫助，能夠建立個人品牌，彰顯專業 自己本身就很熱愛分享知識、技術，而且很享受上台演講的氛圍 看起來很帥 2024 年 1 月，下定決心 一直以來都想架一個自己的個人網站，但遲遲一直沒有下手，絕大部分是因為懶 XD，而我一直以來都會把自己的想法紀錄在 Google Keep 或是 Dropbox Paper\n直到 2024/01 下定決心今年的新希望(目標)一定要架好個人網站，開始寫文章，而且我今年(2024)也準備要畢業了，即將面對職場，這時候再不趕快產出有質量的內容就太虧了\n關於我 Hi 我是 Shiun，家裡有兩隻貓(娜娜 \u0026amp; 妮妮😺😺) 我目前就讀於某私立大學的資管系(2024 畢業)，同時也就職於：\nAWS Educate 5th Cloud Ambassador - Technical Support @AWS Backend Engineer Intern @eGroupAI 在 2024 年 3 月要接任：\nAWS Educate 6th Cloud Ambassador Team Lead (技術) @AWS Cloud Engineer Intern @eCloudValley 主要專精於後端與雲端及 DevOps 方面的技術：\nSpring boot Jenkins AWS Docker Selenium Pytest 除此之外，我也很擅長擔任領導者的角色，在求學職涯中，我幾乎都是擔任領導者的職位，像是在大學畢業專題中我也擔任了組長、PM、Product Owner、Backend Development，在畢業專題(作品連結)中上實踐了敏捷式開發(Scrum, Kanban)","title":"這個網站的第一篇文章 - 關於我 以及架站的動機"}]