【2025新登場・ Android 15 タブレット 大画面】 タブレット 10インチ Wi-Fiモデル 12GB+64GB+1TB-TF拡張 アンドロイド 6000mAh PD18W&Type-C充電 8コアCPU フルメタルボディ 2.4G&5G/WiFi/Bluetooth5.2/顔認識/無線投影/日本語説明書/WidevineL1対応 tablet
¥11,980 (2025年4月30日 13:06 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)Kindle (2024年発売)、6インチディスプレイ電子書籍リーダー、16GBストレージ、ブラック、広告なし
¥19,980 (2025年4月30日 13:11 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
AWSで放置しているリソースがあると怖い、でもそれぞれ個別に確認していくのは面倒…
ということでいくつかの種類のAWSリソースをブラウザから確認できるようなLambda関数を作成し、S3に配置したindex.htmlで参照できるようにしたいと考えました。
これが最適な方法では無いような気がしますが、こういった解決策もあるという一例として記事にしました。
それぞれ解説します。
EventBridge
ここではLambda関数を定期実行します。主にAWSを操作する平日の9時から18時で、あまり実行回数が多いとコストが心配なのでまずは30分に1回だけ実行することにしました。
Lambda
boto3を使ってリソースの名前を取得していき、名前をリストとしたjsonをS3に保存しています。
リソースを自動的に取得するのではなく、個別に設定が必要です。自動取得を求めていた人はごめんなさい!
S3
まずは取得したリソースをresources.jsonというファイルに保存しようと思いました。理由としては情報を出力する方法を決めていなかったので、後々活用しやすいように考えました。
最終的にはwebサイトの形式になり、index.htmlも同じバケットに保存しました。
CloudFront
S3にresource.jsonがある都合上、直接のアクセスはしたくなかったので、CloudFrontからのアクセスに絞っています。
Lambda
まずLambda関数を作ります。
Pythonを使用しました。
import boto3
import json
import os
from datetime import datetime, timezone, timedelta
def get_resource_details(region):
resources = {}
ec2 = boto3.client('ec2', region_name=region)
rds = boto3.client('rds', region_name=region)
s3 = boto3.client('s3', region_name=region)
lambda_client = boto3.client('lambda', region_name=region)
resources['EC2 Instances'] = []
try:
running_instances = ec2.describe_instances(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
for reservation in running_instances['Reservations']:
for instance in reservation['Instances']:
name = ''
for tag in instance.get('Tags', []):
if tag['Key'] == 'Name':
name = tag['Value']
break
resources['EC2 Instances'].append({'Id': instance['InstanceId'], 'Name': name or instance['InstanceId']})
except Exception as e:
resources['EC2 Instances'].append({'Error': str(e)})
resources['RDS Instances'] = []
try:
available_rds = rds.describe_db_instances()
for db_instance in available_rds['DBInstances']:
resources['RDS Instances'].append({'Id': db_instance['DBInstanceIdentifier'], 'Name': db_instance['DBInstanceIdentifier']}) # RDS は Identifier が名前の役割を果たすことが多い
except Exception as e:
resources['RDS Instances'].append({'Error': str(e)})
resources['S3 Buckets'] = []
try:
buckets = s3.list_buckets()
for bucket in buckets.get('Buckets', []):
resources['S3 Buckets'].append({'Name': bucket['Name']})
except Exception as e:
resources['S3 Buckets'].append({'Error': str(e)})
resources['Lambda Functions'] = []
try:
response = lambda_client.list_functions()
for function in response.get('Functions', []):
resources['Lambda Functions'].append({'Name': function['FunctionName']})
while 'NextMarker' in response:
response = lambda_client.list_functions(Marker=response['NextMarker'])
for function in response.get('Functions', []):
resources['Lambda Functions'].append({'Name': function['FunctionName']})
except Exception as e:
resources['Lambda Functions'].append({'Error': str(e)})
# 他の監視したいリソースの詳細情報を取得する処理をここに追加できます
return resources
def lambda_handler(event, context):
region = os.environ.get('AWS_REGION', 'ap-northeast-1') # デフォルトリージョン
s3_bucket_name = os.environ.get('S3_BUCKET_NAME')
s3_key = 'resources.json'
if not s3_bucket_name:
print("S3_BUCKET_NAME 環境変数が設定されていません。")
return {
'statusCode': 500,
'body': json.dumps({'error': 'S3_BUCKET_NAME が設定されていません。'})
}
resource_data = {
'timestamp': datetime.now(timezone(timedelta(hours=9))).isoformat(),
'resources': get_resource_details(region)
}
try:
s3 = boto3.client('s3')
response = s3.put_object(
Bucket=s3_bucket_name,
Key=s3_key,
Body=json.dumps(resource_data, default=str), # デフォルトでシリアライズできない型を文字列に変換
ContentType='application/json'
)
print(f"S3 への書き込みに成功しました: {response}")
return {
'statusCode': 200,
'body': json.dumps({'message': 'リソース詳細情報を S3 に書き込みました。'})
}
except Exception as e:
print(f"S3 への書き込みに失敗しました: {e}")
return {
'statusCode': 500,
'body': json.dumps({'error': f'S3 への書き込みに失敗しました: {str(e)}'})
}
if __name__ == "__main__":
# ローカルテスト用
os.environ['S3_BUCKET_NAME'] = 'your-s3-bucket-name' # 実際のバケット名に置き換えてください
result = lambda_handler(None, None)
print(result)
このプログラムではサンプルとしてEC2、RDS、S3、Lambdaのリソース名を取得しています。他の種類のリソースを取得する場合は、プログラムの内容を参考にget_resource_details関数に各自追加してください。
またresources.jsonを書き込むためのバケット名をS3_BUCKET_NAMEとして、使用するリージョンをAWS_REGIONとして環境変数に追加してください。
S3
ここではindex.htmlの内容を残しておきます。
参考としてざっくり1ファイルで書いているだけなので簡易的なものです。
lang="ja">
charset="UTF-8">
name="viewport" content="width=device-width, initial-scale=1.0">
AWS リソース情報
rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
body {
background-color: #f8f9fa;
}
.container {
margin-top: 30px;
}
.resource-section {
margin-bottom: 20px;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.resource-header {
background-color: #e9ecef;
padding: 0.75rem 1.25rem;
border-bottom: 1px solid #dee2e6;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
}
.resource-body {
padding: 1.25rem;
}
.error {
color: #dc3545;
}
class="container">
id="resource-info">
class="alert alert-info" role="alert">
リソース情報を読み込み中です...
async function fetchResources() {
try {
const response = await fetch('/resources.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
displayResources(data);
} catch (error) {
document.getElementById('resource-info').innerHTML = `
リソース情報の取得に失敗しました: ${error}
`;
console.error("リソース情報の取得に失敗しました:", error);
}
}
function displayResources(data) {
const resourceInfoDiv = document.getElementById('resource-info');
resourceInfoDiv.innerHTML = '';
const timestamp = document.createElement('p');
timestamp.classList.add('text-muted', 'mb-3');
timestamp.textContent = `最終更新: ${new Date(data.timestamp).toLocaleString()}`;
resourceInfoDiv.appendChild(timestamp);
for (const [resourceType, resources] of Object.entries(data.resources)) {
const section = document.createElement('div');
section.classList.add('resource-section');
const header = document.createElement('div');
header.classList.add('resource-header');
const title = document.createElement('h6');
title.classList.add('mb-0');
title.textContent = resourceType;
header.appendChild(title);
section.appendChild(header);
const body = document.createElement('div');
body.classList.add('resource-body');
const list = document.createElement('ul');
list.classList.add('list-unstyled');
if (Array.isArray(resources) && resources.length > 0) {
resources.forEach(item => {
const listItem = document.createElement('li');
if (item.Name) {
listItem.textContent = `${item.Name}`;
} else if (item.Id) {
listItem.textContent = `ID: ${item.Id}`;
} else if (item.Error) {
listItem.classList.add('error');
listItem.textContent = `エラー: ${item.Error}`;
} else {
listItem.textContent = '情報なし';
}
list.appendChild(listItem);
});
} else if (Array.isArray(resources) && resources.length === 0) {
const emptyMessage = document.createElement('p');
emptyMessage.classList.add('text-muted');
emptyMessage.textContent = '該当するリソースはありません。';
body.appendChild(emptyMessage);
} else if (resources && resources.Error) {
const errorMessage = document.createElement('p');
errorMessage.classList.add('error');
errorMessage.textContent = `エラー: ${resources.Error}`;
body.appendChild(errorMessage);
} else {
const noDataMessage = document.createElement('p');
noDataMessage.classList.add('text-muted');
noDataMessage.textContent = 'リソース情報の形式が不正です。';
body.appendChild(noDataMessage);
}
body.appendChild(list);
section.appendChild(body);
resourceInfoDiv.appendChild(section);
}
}
// ページロード時にリソース情報を取得
fetchResources();
// 定期的にリソース情報を更新 (例: 1分ごと)
setInterval(fetchResources, 60000);
"https://code.jquery.com/jquery-3.5.1.slim.min.js">
"https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js">
"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
Lambdaの実行結果をresources.jsonに出力したため、resources.jsonを取得して表示するだけのシンプルな構造になっていると思います。
またセキュリティ上の理由から、S3静的ウェブサイトホスティングは使用せず、パブリックアクセスも完全にブロックしています。
CloudFrontからのアクセスに限定するため、後述するOAC(オリジンアクセス制御)を設定します。
CloudFrontの設定
CloudFrontではS3との接続のためにOACの設定を行いました。
はじめにオリジンの画面でOACの作成を行います。
作成ができたら自動的に生成されるS3バケットにアタッチしましょう。
これをS3のバケットポリシーに貼り付けてうまくいかない場合は、オリジンが静的ウェブサイトホスティングの方になっている可能性があります(1敗)
EventBridge
最後はEventBridgeの設定です。
cronで実行するよう設定しました。
個人的にはcronの設定に詰まったので共有しておきます。cron(0,30 1-9 ? * MON-FRI *)
0,30という部分は30分に1回実行するために、”分”が0分のときまたは30分のときに実行する設定になっています。
1-9はUTCで1時から9時まで実行するように設定されています。
MON-FRIは平日に実行するように指定されており、この設定を行うためには3つ目の”日”の指定にあたる項目に?を入れておく必要があります。
結果
最後に出来上がった画面です。
一部隠していますが、このように出力することができました!
ちなみにこのままだとこのページのリンクを開けば誰でも見られてしまう状態なので、自分の場合はWAFでアクセスできるIPを絞る方法で対処しました。
おわりに
このプログラムによって不要なサービスが可視化され、より理解しやすくなりました!
リソースを定期的に取得し、それをブラウザから見られるようにするという基本的なアプリケーションモデルの勉強にもなったと思います。
Views: 0