跳至主要內容

標籤「AWS」的 9 篇文章

查看所有標籤

新功能 Amazon S3 Files 讓 S3 桶可以掛載到 EC2

Amazon S3 Files 是一項可以將 S3 桶直接掛載到 EC2 等計算資源的服務,並且以 NFS 檔案系統的形式運行。資料保持在 S3 中,可以使用一般的檔案操作(ls、cp、cat 等)進行讀寫。

S3 Files 是什麼​

S3 Files 是基於 Amazon EFS 建立的共享檔案系統,讓使用者能以檔案系統的形式存取 S3 桶中的數據。

主要特點如下:

項目內容
協定NFS 4.1 / 4.2
支援的計算資源EC2、Lambda、ECS、EKS
同時連接數量最多 25,000 個計算資源
讀取吞吐量最大 TB/秒
IOPS超過 1,000 萬/桶
加密TLS(傳輸中)+ AWS KMS(儲存時)
檔案系統功能POSIX 權限、檔案鎖定、讀取後寫入一致性

運作原理​

S3 Files 將被訪問的資料自動載入高效能儲存系統,並以低延遲的方式提供服務。

  • 小檔案(預設小於 128 KB):直接從高效能儲存中讀取
  • 大檔案(1 MB 以上):直接從 S3 流式傳輸
  • 寫入:在高效能儲存中寫入後,自動同步到 S3

高效能儲存中的資料,如果在一定時間內(預設 30 天,可設置為 1 - 365 天)未被訪問,將自動刪除。

前提條件​

  • AWS 帳戶
  • EC2 實例(Linux)
  • S3 桶(與 EC2 相同區域)
  • 兩個 IAM 角色
    • 建立檔案系統用:對 S3 桶的讀寫權限
    • EC2 實例用:附加 AmazonS3FilesClientFullAccess 管理策略
  • 安全群組:允許 NFS 的 2049 端口通訊

IAM 角色的建立​

S3 Files 需要兩個 IAM 角色。

1. 用於建立檔案系統的角色​

使用管理控制台時會自動創建,因此不需要手動創建

此角色用於讓 S3 Files 存取桶。

# 創建角色
aws iam create-role \
--role-name S3Files-FileSystem-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "s3files.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}'

# 附加 S3 Files 客戶端策略
aws iam attach-role-policy \
--role-name S3Files-FileSystem-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess

在創建檔案系統時用 --role-arn 指定此角色。

2. 用於 EC2 實例的角色​

未附加 IAM 角色會導致掛載失敗

在 CloudShell 中創建以下角色。

# 創建角色
aws iam create-role \
--role-name EC2-S3Files-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}'

# 附加 S3 Files 客戶端策略
aws iam attach-role-policy \
--role-name EC2-S3Files-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess

# 創建並附加實例檔案配置
aws iam create-instance-profile \
--instance-profile-name EC2-S3Files-Profile

aws iam add-role-to-instance-profile \
--instance-profile-name EC2-S3Files-Profile \
--role-name EC2-S3Files-Role

將此角色附加到實例上。

設定步驟​

1. 準備 S3 桶​

在 S3 控制台中創建通用桶,也可以使用現有的桶。

需要特別注意的是,必須啟用桶的版本控制設定

2. 創建檔案系統​

從控制台創建​

alt text

  1. 在 S3 控制台中選擇桶
  2. 點擊「檔案系統」標籤 → 「創建檔案系統」

從控制台創建後,所有可用區會自動創建掛載目標和存取點。

alt text

  1. 指定前綴和 VPC,然後點擊「創建檔案系統」。

記錄下輸出的檔案系統 ID(例如:fs-0123456789abcdef0)。

3. 在實例中掛載​

在終端中執行以下命令。

# 創建掛載點
sudo mkdir /mnt/s3files

# 挂載
sudo mount -t s3files fs-0123456789abcdef0:/ /mnt/s3files
備註

如果無法掛載,請執行以下命令再試一次:

sudo dnf install -y amazon-efs-utils # Amazon Linux, RHEL
# sudo apt install -y amazon-efs-utils (Ubuntu, Debian)
備註

如果在執行 dnf 命令時通訊出現問題,請設置 S3 端點(網關),並指定與實例相同的可用區。

但要注意,S3 端點的路由表應與實例所在的子網相同。

確認掛載情況:

df -h /mnt/s3files

應顯示類似以下內容:

Filesystem Size Used Avail Use% Mounted on
<s3files-dns> 8.0E 129M 8.0E 1% /mnt/s3files

4. 確認運行​

cd /mnt/s3files

# 創建檔案
sudo sh -c 'echo "Hello, s3 Files!" > test.txt'

# 讀取檔案
cat test.txt

# 創建目錄
sudo mkdir test-directory

ls -la

# 複製檔案
sudo cp test.txt test-directory/

cd test-directory/

# 確認檔案列表
ls -la

寫入的檔案會在約 1 分鐘內與 S3 桶同步。可以在 S3 控制台確認對象已創建。

aws s3 ls s3://<bucket-name>/

自動掛載設定​

為了在重啟後保持掛載,需要將以下內容添加到 /etc/fstab。

# 添加到 /etc/fstab
fs-0123456789abcdef0:/ /mnt/s3files s3files _netdev,nofail 0 0

_netdev 是一個選項,確保在網路連接後再進行掛載,這是必需的。加入 nofail 可以防止掛載失敗時導致實例無法啟動。

收費​

S3 Files 的收費如下:

  • 高效能儲存使用量:檔案系統中數據的儲存費用
  • 檔案系統存取費用:對高效能儲存進行讀寫操作的費用
  • S3 請求費用:對於直接從 S3 讀取大於 1 MB 的檔案,僅收取 S3 GET 費用

為按量計費的無需配置模式,根據 AWS 的說法,與傳統 S3 和檔案系統間數據複製相比可減少最多 90% 的成本。

總結​

  • 使用 S3 Files 可以將 S3 桶掛載為 NFS 檔案系統在 EC2 上
  • 數據保留在 S3 中,可以使用 ls、cat、cp 等正常的檔案操作
  • 透過高效能儲存提供低延遲,未被訪問的數據會自動退避
  • 通過 /etc/fstab 設定自動掛載,即使重啟後也能維持

參考​

在 Amazon Cognito 設定密碼金鑰 (WebAuthn)

在使用 AWS SAM 建立的檔案儲存 API 的使用者認證中,我使用了 Amazon Cognito。最近我新增了透過密碼金鑰 (WebAuthn) 登入的功能,因此總結一下設定內容。

前提條件:使用密碼金鑰所需的 Cognito 設定​

要在 Cognito 中使用密碼金鑰,必須具備以下所有條件。

要件本次的架構
UserPool 層級ESSENTIALS 以上
管理式登入v2 (新的登入 UI)
自訂網域login.example.com (用於 Relying Party ID)

Cognito 的密碼金鑰必須從管理式登入 v2 UI 中註冊及使用。在 LITE 層級 (免費) 中無法使用 WebAuthn,因此需要 ESSENTIALS 層級。

認證流程​

密碼金鑰註冊流程​

第一次登入時使用密碼,然後從帳戶設定中註冊密碼金鑰。

密碼金鑰登入流程​

註冊後,可以透過「使用密碼金鑰登入」按鈕直接認證。

設定內容​

為了新增密碼金鑰,我在 template.yaml (SAM 模板) 上進行了僅 6 行的變更。

變更前​

UserPool:
Type: AWS::Cognito::UserPool
Properties:
# ...
Policies:
PasswordPolicy:
MinimumLength: 8
# ...
MfaConfiguration: "OFF"

變更後​

UserPool:
Type: AWS::Cognito::UserPool
Properties:
# ...
Policies:
PasswordPolicy:
MinimumLength: 8
# ...
SignInPolicy:
AllowedFirstAuthFactors:
- PASSWORD
- WEB_AUTHN # ← 新增密碼金鑰
MfaConfiguration: "OFF"
WebAuthnRelyingPartyID: login.example.com # ← 指定 RP ID
WebAuthnUserVerification: required # ← 強制要求生物認證

各參數的意義​

SignInPolicy.AllowedFirstAuthFactors​

初步認證步驟中可用的認證方式列表。只有 PASSWORD 時僅能使用密碼,若加上 WEB_AUTHN 則能選擇密碼金鑰。

WebAuthnRelyingPartyID​

WebAuthn 的 Relying Party ID (RP ID)。密碼金鑰將綁定到此網域生成和儲存,因此必須與 實際提供登入頁面的網域一致。

本次直接使用自訂網域 login.example.com。若使用 Cognito 的預設網域 (xxx.auth.ap-northeast-1.amazoncognito.com),則需指定該網域。

WebAuthnUserVerification​

使用密碼金鑰時的用戶確認等級。

值說明
required強制要求生物認證或 PIN 等的身份確認
preferred儘可能要求身份確認,但可不要求
discouraged省略身份確認 (不使用生物認證等)

為了提高安全性,選擇了 required。

管理式登入的 UI​

在管理式登入 v2 的界面中,密碼金鑰設定後,登入畫面將自動新增「使用密碼金鑰登入」按鈕。第一次註冊時需先使用密碼登入,然後可在帳戶設定中新增密碼金鑰。

部署​

sam build
sam deploy --no-confirm-changeset

由於 samconfig.toml 中已定義堆疊名稱、區域及參數,因此每次部署時不需要指定選項。

總結​

啟用 Cognito 密碼金鑰的重點總結如下:

  1. 設定為 ESSENTIALS 層級 (LITE 不支持 WebAuthn)
  2. 使用 管理式登入 v2
  3. 指定自訂網域 (或 Cognito 預設網域) 為 RP ID
  4. 在 SignInPolicy.AllowedFirstAuthFactors 中增加 WEB_AUTHN
  5. 使用 WebAuthnUserVerification: required 強制要求生物認證

僅透過 6 行的變更,即可使用密碼金鑰登入。此時仍保留密碼,逐步向密碼金鑰過渡,這正是 Cognito 的便利之處。

比較 Anthropic API 與 AWS Bedrock 的費用

Claude 若要透過 API 使用,除了直接使用 Anthropic API 外,也能經由 AWS Bedrock、Google Vertex AI、Microsoft Azure (Azure AI Foundry) 使用。基本價格各路徑幾乎相同,但在批次處理與與雲端生態系統整合面會有差異。

單位:USD / 1M 代幣 (MTok)。資料截至 2026 年 3 月。

基本價格(按需)​

模型項目Anthropic APIBedrockVertex AIAzure
Claude Opus 4.6輸入$5.00$5.00$5.00$5.00
輸出$25.00$25.00$25.00$25.00
Claude Sonnet 4.6輸入$3.00$3.00$3.00$3.00
輸出$15.00$15.00$15.00$15.00
Claude Haiku 4.5輸入$1.00$1.00$1.00$1.00
輸出$5.00$5.00$5.00$5.00
Claude Sonnet 4.5輸入$3.00$3.00$3.00$3.00
輸出$15.00$15.00$15.00$15.00

基本價格在各路徑相同。

不過在 Vertex AI 若不是使用全球端點而指定區域端點(regional endpoint),標準價格會額外加收 10%。Bedrock 有 Long Context 變體(另有 SKU),但價格相同。Anthropic API 則已將 Long Context 整合在一般模型中。

快取費用​

Prompt 快取(Prompt Caching)的費用在各路徑也相同。

模型快取類型Anthropic APIBedrockVertex AIAzure
Claude Opus 4.65 分快取寫入$6.25$6.25$6.25$6.25
1 小時快取寫入$10.00$10.00$10.00$10.00
快取讀取$0.50$0.50$0.50$0.50
Claude Sonnet 4.65 分快取寫入$3.75$3.75$3.75$3.75
1 小時快取寫入$6.00$6.00$6.00$6.00
快取讀取$0.30$0.30$0.30$0.30
Claude Haiku 4.55 分快取寫入$1.25$1.25$1.25$1.25
1 小時快取寫入$2.00$2.00$2.00$2.00
快取讀取$0.10$0.10$0.10$0.10

快取寫入依 TTL 分為 5 分(短期)和 1 小時(長期)兩種。長期快取的寫入成本較高,但如果系統提示(system prompt)很長且會被重複參考,透過節省讀取費用通常是划得來的。

批次處理費用​

Bedrock、Vertex AI、Anthropic API 三者的非同步批次 API 可享按需價格的 50% 折扣。Azure 目前未明示。

模型批次輸入批次輸出
Claude Opus 4.6$2.50$12.50
Claude Sonnet 4.6$1.50$7.50
Claude Haiku 4.5$0.50$2.50
Claude Sonnet 4.5$1.50$7.50

若要大量處理資料(例如日誌分析、向量嵌入生成等)並頻繁使用批次處理,無論走哪個路徑都能把成本砍半。

生態系比較​

比較重點Anthropic APIBedrockVertex AIAzure
基本價格相同相同相同相同
區域加價——+10%(區域端點)—
批次處理(50% OFF)○○○未明示
東京區域—○○—
IAM / 審計日誌整合—AWSGoogle CloudAzure
VPC / PrivateLink—○○○
計費整合Anthropic 直接AWSGoogle CloudAzure
新功能推出速度最快會延遲會延遲會延遲

新功能(例如 Extended Thinking)通常會先在 Anthropic API 上推出,向 Vertex AI、Bedrock、Azure 的推展有時會晚幾週。

如何選擇​

  • 單純使用/原型開發:Anthropic API,只要一組 API 金鑰即可啟動,新功能也能最先使用。
  • 已整合到 AWS:若需 IAM、CloudWatch、VPC,選 Bedrock。支援東京區域。
  • 已整合到 Google Cloud:Vertex AI 是自然的選擇,但注意區域端點會加收 10%。
  • 已整合到 Azure:可透過 Azure AI Foundry 使用,能整合到 Azure 的計費與管理。
  • 大量使用批次以節省成本:Bedrock、Vertex AI、Anthropic API 都有 50% OFF 的批次 API 可用。

參考​

在 AWS 無伺服器環境下自建部落格留言 API

想要在這個部落格加入留言功能,因此在 AWS 無伺服器架構下自建了一個 API。介紹其設計與實作。

架構​

請求以以下流程處理。

瀏覽器 (www.hikari-dev.com)
↓ HTTPS
API Gateway
├── GET /comment?postId=... → 取得留言
├── POST /comment → 發表留言
└── PATCH /comment/{id} → 管理(切換隱藏)
↓
Lambda (Node.js 20 / arm64)
↓
DynamoDB(儲存留言)
+ SES v2(通知管理者)

程式碼以 TypeScript 撰寫,並以 SAM(Serverless Application Model)管理基礎設施即程式碼(IaC)。Lambda 採用 arm64(Graviton2)以稍微節省成本。

DynamoDB 的資料表設計​

資料表名稱為 blog-comments,分區鍵為 postId,排序鍵為 commentId。

鍵型別說明
postIdString文章的識別值(例: /blog/2026/03/20/hime)
commentIdStringULID(可時序排序的 ID)

因為在排序鍵使用 ULID,使用 QueryCommand 取得的留言會自動按發表順序排列。這也是為什麼沒有使用 UUID 的原因。

垃圾留言過濾​

在將留言寫入 DynamoDB 之前,會與 keywords.json 中定義的關鍵字比對。

若命中關鍵字,會以 isHidden: true 自動隱藏,並附加 isFlagged: "1"。若未命中則立即公開。

isFlagged 作為稀疏 GSI(Sparse GSI)的鍵使用。未命中的留言不會帶入此屬性,因此不會在 GSI 中增加多餘的分區,對成本與效能都有利。只要在 DynamoDB Document Client 設定 removeUndefinedValues: true 就能達成。

通知管理者的郵件​

每次有留言發表時,會用 SES v2 發信通知自己。郵件內容包含發言者名稱、內容、評分、IP 位址以及是否被標記(flag)等資訊。

郵件發送採非同步進行,即使發送失敗也會忽略錯誤(不影響使用者流程)。這是為了避免影響留言發表的回應速度。

隱私考量​

雖然會在 DynamoDB 中儲存 IP 位址與 User-Agent,但不會把它們包含在 GET 端點的回應中。我們在型別定義層就進行分離。

安全性​

層級對策
網路使用 AWS WAF 設定每個 IP 每 5 分鐘 100 次的速率限制
CORS僅允許 https://www.hikari-dev.com
管理 APIAPI Gateway 的 API Key 認證(X-Api-Key 標頭)
垃圾留言關鍵字過濾自動隱藏

管理端點(PATCH /comment/{id})只要在 SAM 模板設定 ApiKeyRequired: true 即可啟用 API 金鑰認證。無需自行實作 Lambda Authorizer,較為簡單。

總結​

採用無伺服器架構後不需管理伺服器,DynamoDB 的按需付費讓流量低的個人部落格也能把成本降到最低。

程式碼以 SAM + TypeScript + esbuild 打包,僅需執行 sam build && sam deploy 即可部署。

用 AWS 無伺服器打造雲端儲存服務

はじめに​

自分専用のファイル共有システムが欲しいと思い、AWS のサーバーレスサービスだけでファイルストレージサービスを作りました。

この記事では、設計で意識したポイントと、実際のアーキテクチャを紹介します。

何を作ったか​

制作した Web システムは、Web ブラウザからファイルのアップロード・ダウンロード・フォルダ管理ができるクラウドストレージサービスです。

主な機能​

  • ファイルのアップロード / ダウンロード
  • フォルダの作成・階層管理
  • 複数ファイル / フォルダの一括 ZIP ダウンロード
  • ユーザー認証(サインアップ・ログイン・パスワードリセット)
  • ユーザープロフィール管理

アーキテクチャ​

以下、構成図です。

認証の大部分は Cognito で行い、 ファイル転送は S3 の Presigned URL を Lambda で発行してクライアントと S3 が直接やり取りする仕組みです。

技術スタック​

レイヤー技術
バックエンドC# (.NET 8) / AWS Lambda
認証Amazon Cognito + Managed Login v2
APIAPI Gateway (REST) + Cognito Authorizer
ストレージAmazon S3

設計判断とその理由​

認証を Cognito で行う​

Cognito の OAuth 2.0 エンドポイントと Managed Login を活用し、認証機能を実現しました。

最終的に認証系の Lambda は TokenFunction 1 つだけになりました。

機能的にもセキュリティ的にも減らせるコードは減らすのが吉です。

AWS のサービスがやってくれることを自前で書く必要はありません。

Presigned URL によるファイル転送​

ファイルのアップロード・ダウンロードで Lambda を経由すると、いくつかの問題が生じます:

  • Lambda のペイロード上限に引っかかる
  • 大きなファイルを Lambda のメモリに載せるとコストがかかる
  • 転送時間が Lambda の実行時間としてカウントされる

Presigned URL なら、Lambda は URL を発行するだけで、実際のファイル転送はブラウザと S3 が直接行います。

Lambda の実行時間は数十ミリ秒で済み、ファイルサイズの制約も S3 の上限までとなります。

アップロードの流れ:
1. ブラウザ → Lambda: 「file.pdf をアップロードしたい!アップロード先の URL を送れ~」
2. Lambda → ブラウザ: 「アップロード先の Presigned URL だよ。ここに PUT してね~」
3. ブラウザ → S3: 「S3 に PUT するよ~」
4. ブラウザ → Lambda: 「アップロード完了したよ~」

4. フォルダの ZIP ダウンロード​

S3 にはフォルダごとダウンロードする機能がありません。

複数ファイルの一括ダウンロードは、Lambda 上で ZIP を生成して一時的に S3 に置き、その Presigned URL を返す方式にしました。

一時 ZIP ファイルは S3 のライフサイクルルールで 1 日後に自動削除されるので、ゴミが溜まることはありません。

セキュリティ​

対策実装
ブルートフォース防止Cognito 標準のロック機能 (5 回失敗: 15 分ロック)
API 保護Cognito Authorizer による JWT 検証
CORSAllowedOrigin を特定のドメインに限定
一時ファイル管理S3 ライフサイクルで不要なファイルを 1 日で自動削除

コスト​

サーバーレス構成なので、利用がなければコストはほぼゼロです。

  • Cognito: ESSENTIALS Tier は MAU 10,000 まで無料
  • Lambda: 月 100 万リクエストまで無料
  • S3: 保存量に応じた従量課金(GB あたり約 $0.025/月)
  • API Gateway: 100 万リクエストあたり $3.50

個人利用なら月額数十円〜数百円程度に収まります。

インフラのコード化​

インフラ全体を 1 つの template.yaml (AWS SAM) で定義しています。

Cognito User Pool、API Gateway、Lambda 3 関数、S3 バケット、CloudWatch アラーム、SNS — すべてのリソースを 600 行程度の YAML で定義しています。

EC2 Instance Connect 在 Windows 上無法在沒有金鑰時連線

在 Windows 上無法連線到 Instance Connect​

PS C:\> aws ec2-instance-connect ssh --instance-id i-0aa38de21acf2aa1c --region ap-south-1
Bad permissions. Try removing permissions for user: \\OWNER RIGHTS (S-1-3-4) on file C:/Users/hikari/AppData/Local/Temp/tmpm9m1bf7j/private-key.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions for 'C:\\Users\\hikari\\AppData\\Local\\Temp\\tmpm9m1bf7j\\private-key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "C:\\Users\\hikari\\AppData\\Local\\Temp\\tmpm9m1bf7j\\private-key": bad permissions
[email protected]: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

截至 2025/06/11 的驗證。

從 WSL 可以登入​

PS C:\> wsl -- aws ec2-instance-connect ssh --instance-id i-0aa38de21acf2aa1c --region ap-south-1
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
Last login: Tue Jun 10 22:50:33 2025 from 192.168.0.183
[ec2-user@ip-192-168-0-4 ~]$

為什麼?

補充​

降級後就能連線。

希望能修正。

參考: https://github.com/aws/aws-cli/issues/9114

msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2-2.17.35.msi

EC2 Instance Connect 總結

EC2 Instance Connect 是什麼​

EC2 Instance Connect 是用來簡化對 AWS EC2 執行個體的 SSH 連線的服務。

過去的 SSH 連線方式需要事先在執行個體放置公開金鑰,但使用 EC2 Instance Connect 可以將臨時的 SSH 公開金鑰傳送到執行個體以建立連線。(不過,除部分 AMI 外,需要安裝 Instance Connect 的套件)

連線到執行個體的方法​

連線到執行個體的方法有好幾種。

① 直接從網際網路連線(與 Instance Connect 無關)​

直接從網際網路連線的方式需要經由 Internet Gateway 或 NAT Gateway,且需要公有 IP 位址,因此若限定在私有網路則無法使用。

可以直接使用 ssh 指令,因此是最簡單的方式。

ssh <使用者名稱>@<公有 IP 位址>

② 透過 EC2 Instance Connect 端點的連線​

使用 AWS CLI 並透過 EC2 Instance Connect 端點連線時,不需要公有 IP 位址。

另外,還可以省下那部分的費用(每月數百日圓)。

使用 AWS CLI 可用類似下面的指令連線,但需事先匯入金鑰對並為執行個體設定金鑰對。

例如可以使用以下指令:

aws ec2-instance-connect ssh --private-key-file .ssh/id_ed25519 --os-user <使用者名稱> --instance-id <執行個體 ID> --connection-type eice

※ 不過,要事先取得存取金鑰並用 aws configure 設定好。

如果不想讓執行個體連到網際網路,且想使用非官方 AMI,採用此連線方式最合適。

③ 從 AWS 管理主控台用 Instance Connect 連線​

若是 Amazon Linux 或 Ubuntu,只要建立 Instance Connect 端點,就可以從管理主控台連到執行個體。

不過,除部分 AMI 外,仍需安裝 Instance Connect 的套件。

詳情請見:https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html

④ 其他連線方式​

透過 Session Manager 連線​

需為 Session Manager 建立兩個端點,並將允許透過 Session Manager 連線的 IAM 角色附加到執行個體。

此外,除部分 AMI 外,需安裝 Session Manager 的套件。

透過 EC2 序列埠主控台 (Serial Console) 連線​

使用序列埠主控台可以直接連到執行個體。請注意,若未設定密碼就無法登入。

安全性設定​

網路 ACL(執行個體所在子網)​

預設會允許所有流量,因此若使用預設設定通常不需要特別調整。

以下列出最基本需要的設定。

入站規則​

入站規則需允許 SSH(22)。

因為執行個體的 SSH 伺服器預設使用 22 埠,故需允許該埠。

出站規則​

出站規則需允許自訂 TCP(1024-65535)。

1024-65535 是 SSH 連線時客戶端會使用的埠範圍。

安全性群組(執行個體)​

入站規則​

入站規則需允許 SSH(22)。

此設定為必要。

出站規則​

安全性群組是有狀態(stateful)的,所以通常不需要設定出站規則。

安全性群組(EC2 Instance Connect 端點)​

入站規則​

因為為有狀態,通常不需要設定入站規則。

出站規則​

需允許 SSH(22)。

因為要對執行個體的 22 埠進行通訊,所以必須允許此埠。

在 AWS 使用官方 Rocky Linux 映像

AMI 的選擇方式​

從官方頁面取得 AMI。

https://rockylinux.org/ja-JP/download

選擇要設定給實例的架構 (ARM (aarch64)),並選擇 Cloud Images 裡的 AWS AMI。

alt text

以版本號過濾,找到符合條件的映像。

alt text

AMI ID 無法直接複製,因此點擊 Deploy 按鈕,然後從 AWS 主控台複製。

用 AMI ID 搜尋會出現如下

alt text

用擁有者過濾會比較好。

擁有者 = 792107900819

alt text

事前準備​

  • 註冊 Key pair
    • 事先執行 ssh-keygen -t ed25519 指令產生公鑰,將 .ssh/id_ed25519.pub 匯入成 Key pair
  • 安裝 AWS CLI
    • 安裝 CLI
    • 設定存取金鑰 (aws configure)

建立網路​

比起 NAT Gateway,使用公開 IP 比較便宜,所以建立 Elastic IP。

架構圖大概長這樣。

建立 EC2 Instance Connect 端點​

alt text

建立 EC2 Instance Connect 端點後,可以從 AWS CLI 登入。

建立實例​

  • 為了接受 ping 要允許 ICMP(Echo Request)(安全性群組)
  • 允許 SSH 連線(安全性群組)
  • 在孟買區域 (Mumbai) 且 arm64 比較便宜
  • 每 vCPU 需要 1.5 GiB RAM(至少 t4g.medium)

因此我用以下條件建立。

  • 區域:孟買(ap-south-1)
  • 架構:arm64
  • AMI:RHEL 8.10 (LVM, aarch64); ami-0415efd8380284dc4
  • 實例類型:t4g.medium
  • Key pair:在 PC 上建立的公鑰 (.ssh/id_ed25519.pub)
  • 網路:公共子網(關聯了到 Internet Gateway 的路由表)
  • 安全性群組:建立安全性群組(名稱為預設)
    • ssh, 0.0.0.0/0
    • 自訂 ICMP - IPv4(Echo Request), 0.0.0.0/0
  • 儲存:1x 10GiB, gp3

連線​

在 PC 上打開終端機,執行以下指令。

aws ec2-instance-connect ssh --private-key-file .ssh/id_ed25519 --os-user rocky --instance-id i-*****************

安裝 Instance Connect 套件​

Rocky Linux 的 AMI 映像沒有包含 Instance Connect 套件,無法從管理控制台連線。因此需要安裝套件。

參考 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html 下載套件。

  • ※選擇 RHEL 的套件
  • ※注意作業系統主版本與架構不同可能無法正常運作

範例​

curl https://amazon-ec2-instance-connect-us-west-2.s3.us-west-2.amazonaws.com/latest/linux_arm64/ec2-instance-connect.rhel8.rpm -o /tmp/ec2-instance-connect.rpm
curl https://amazon-ec2-instance-connect-us-west-2.s3.us-west-2.amazonaws.com/latest/linux_amd64/ec2-instance-connect-selinux.noarch.rpm -o /tmp/ec2-instance-connect-selinux.rpm
sudo dnf install -y /tmp/ec2-instance-connect.rpm /tmp/ec2-instance-connect-selinux.rpm

安裝完成後,就可以從 AWS 管理控制台存取。

alt text

CDK (typescript)​

我做了 CDK 範例,放上來供參考。

請記得更改 keyName(Key pair)的名稱。

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export interface RockyLinuxStackProps extends cdk.StackProps {
}

export class RockyLinuxStack extends cdk.Stack {
public constructor(scope: cdk.App, id: string, props: RockyLinuxStackProps = {}) {
super(scope, id, props);

// Resources
const ec2dhcpOptions = new ec2.CfnDHCPOptions(this, 'EC2DHCPOptions', {
domainName: 'ap-south-1.compute.internal',
domainNameServers: [
'AmazonProvidedDNS',
],
],
});
ec2dhcpOptions.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2InternetGateway = new ec2.CfnInternetGateway(this, 'EC2InternetGateway', {
{
value: 'igw',
key: 'Name',
},
],
});
ec2InternetGateway.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2vpc = new ec2.CfnVPC(this, 'EC2VPC', {
cidrBlock: '10.0.0.0/16',
enableDnsSupport: true,
instanceTenancy: 'default',
enableDnsHostnames: true,
{
value: 'vpc',
key: 'Name',
},
],
});
ec2vpc.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2VPCGatewayAttachment = new ec2.CfnVPCGatewayAttachment(this, 'EC2VPCGatewayAttachment', {
vpcId: ec2vpc.ref,
internetGatewayId: ec2InternetGateway.ref,
});
ec2VPCGatewayAttachment.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2NetworkAcl = new ec2.CfnNetworkAcl(this, 'EC2NetworkAcl', {
vpcId: ec2vpc.ref,
],
});
ec2NetworkAcl.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2RouteTable = new ec2.CfnRouteTable(this, 'EC2RouteTable', {
vpcId: ec2vpc.ref,
});
ec2RouteTable.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SecurityGroup = new ec2.CfnSecurityGroup(this, 'EC2SecurityGroup', {
groupDescription: 'launch-wizard-1 created 2025-04-27T00:11:58.641Z',
groupName: 'launch-wizard-1',
vpcId: ec2vpc.ref,
securityGroupIngress: [
{
cidrIp: '0.0.0.0/0',
ipProtocol: 'tcp',
fromPort: 22,
toPort: 22,
},
{
cidrIp: '0.0.0.0/0',
ipProtocol: 'icmp',
fromPort: 8,
toPort: -1,
},
],
securityGroupEgress: [
{
cidrIp: '0.0.0.0/0',
ipProtocol: '-1',
fromPort: -1,
toPort: -1,
},
],
});
ec2SecurityGroup.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2Subnet = new ec2.CfnSubnet(this, 'EC2Subnet', {
vpcId: ec2vpc.ref,
mapPublicIpOnLaunch: false,
enableDns64: false,
availabilityZoneId: 'aps1-az1',
privateDnsNameOptionsOnLaunch: {
EnableResourceNameDnsARecord: false,
HostnameType: 'ip-name',
EnableResourceNameDnsAAAARecord: false,
},
cidrBlock: '10.0.0.0/20',
ipv6Native: false,
{
value: 'subnet-public1-ap-south-1a',
key: 'Name',
},
],
});
ec2Subnet.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2InstanceConnectEndpoint = new ec2.CfnInstanceConnectEndpoint(this, 'EC2InstanceConnectEndpoint', {
preserveClientIp: false,
securityGroupIds: [
ec2SecurityGroup.attrGroupId,
],
subnetId: ec2Subnet.attrSubnetId,
});
ec2InstanceConnectEndpoint.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2vpcdhcpOptionsAssociation = new ec2.CfnVPCDHCPOptionsAssociation(this, 'EC2VPCDHCPOptionsAssociation', {
vpcId: ec2vpc.ref,
dhcpOptionsId: ec2dhcpOptions.ref,
});
ec2vpcdhcpOptionsAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2RouteHg = new ec2.CfnRoute(this, 'EC2RouteHG', {
routeTableId: ec2RouteTable.ref,
destinationCidrBlock: '0.0.0.0/0',
gatewayId: ec2InternetGateway.ref,
});
ec2RouteHg.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SubnetNetworkAclAssociation = new ec2.CfnSubnetNetworkAclAssociation(this, 'EC2SubnetNetworkAclAssociation', {
networkAclId: ec2NetworkAcl.ref,
subnetId: ec2Subnet.ref,
});
ec2SubnetNetworkAclAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2SubnetRouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, 'EC2SubnetRouteTableAssociation', {
routeTableId: ec2RouteTable.ref,
subnetId: ec2Subnet.ref,
});
ec2SubnetRouteTableAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2Instance = new ec2.CfnInstance(this, 'EC2Instance', {
tenancy: 'default',
instanceInitiatedShutdownBehavior: 'stop',
cpuOptions: {
threadsPerCore: 1,
coreCount: 2,
},
blockDeviceMappings: [
{
ebs: {
volumeType: 'gp3',
iops: 3000,
volumeSize: 10,
encrypted: false,
deleteOnTermination: true,
},
deviceName: '/dev/sda1',
},
],
availabilityZone: 'ap-south-1a',
privateDnsNameOptions: {
enableResourceNameDnsARecord: false,
hostnameType: 'ip-name',
enableResourceNameDnsAaaaRecord: false,
},
ebsOptimized: true,
disableApiTermination: false,
keyName: 'hikari',
sourceDestCheck: true,
placementGroupName: '',
networkInterfaces: [
{
privateIpAddresses: [
{
privateIpAddress: '10.0.3.59',
primary: true,
},
],
secondaryPrivateIpAddressCount: 0,
deviceIndex: '0',
groupSet: [
ec2SecurityGroup.ref,
],
ipv6Addresses: [
],
subnetId: ec2Subnet.ref,
associatePublicIpAddress: true,
deleteOnTermination: true,
},
],
imageId: 'ami-0415efd8380284dc4',
instanceType: 't4g.medium',
monitoring: false,
],
creditSpecification: {
cpuCredits: 'unlimited',
},
});
ec2Instance.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2ElasticIp = new ec2.CfnEIP(this, 'EC2ElasticIp', {
domain: 'vpc',
{
key: 'Name',
value: 'elastic-ip',
},
],
});
ec2ElasticIp.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;

const ec2EipAssociation = new ec2.CfnEIPAssociation(this, 'EC2EipAssociation', {
eip: ec2ElasticIp.ref,
instanceId: ec2Instance.ref,
});
ec2EipAssociation.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
}
}