跳至主要內容

標籤「AWS」的 9 篇文章

查看所有標籤

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

· 6 分鐘閱讀

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

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 中,可以使用 lscatcp 等正常的檔案操作
  • 透過高效能儲存提供低延遲,未被訪問的數據會自動退避
  • 通過 /etc/fstab 設定自動掛載,即使重啟後也能維持

參考

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

· 4 分鐘閱讀

在使用 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 的費用

· 3 分鐘閱讀

Claude 若要透過 API 使用,除了直接使用 Anthropic API 外,也能經由 AWS BedrockGoogle Vertex AIMicrosoft 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

· 3 分鐘閱讀

想要在這個部落格加入留言功能,因此在 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 無伺服器打造雲端儲存服務

· 4 分鐘閱讀

はじめに

自分専用のファイル共有システムが欲しいと思い、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 上無法在沒有金鑰時連線

· 1 分鐘閱讀

在 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
ec2-user@192.168.0.4: 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 總結

· 3 分鐘閱讀

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 映像

· 5 分鐘閱讀

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;
}
}