
Huawei Cloud Eip Cost Optimizer
- 67 installs
- 19 repo stars
- Updated July 31, 2026
- huaweicloud/huaweicloud-skills
List Huawei Cloud EIPs across regions, identify idle/unbound EIPs, and generate HTML/JSON cost-optimization reports, read-only with idle alerts.
About
Provides read-only EIP cost analysis: lists Elastic IPs across regions, flags idle/unbound EIPs, generates cost reports, and sets up idle monitoring with webhook/email alerts. A developer uses it to find wasted EIP spend without any release or bandwidth changes.
- Identifies idle/unbound EIPs and generates cost reports
- Read-only with webhook/email idle alerts and audit logs
Huawei Cloud Eip Cost Optimizer by the numbers
- 67 all-time installs (skills.sh)
- +5 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #669 of 1,042 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/huaweicloud/huaweicloud-skills --skill huawei-cloud-eip-cost-optimizerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 67 |
|---|---|
| repo stars | ★ 19 |
| Last updated | July 31, 2026 |
| Repository | huaweicloud/huaweicloud-skills ↗ |
What it does
List Huawei Cloud EIPs across regions, identify idle/unbound EIPs, and generate HTML/JSON cost-optimization reports, read-only with idle alerts.
Files
Huawei Cloud EIP Cost Optimizer
Overview
This skill provides batch management and cost optimization capabilities for Huawei Cloud Elastic Public IPs (EIPs).
Architecture: Python SDK v2 → EIP Service API → VPC/Bandwidth/Tag resources
Related Skills: For broader cost optimization across all resource types (ECS, EVS, OBS, etc.), see the archived huaweicloud-cost-optimizer skill. This skill focuses exclusively on EIP optimization with deeper functionality and 100% Python SDK compliance.
- Periodic cleanup of idle EIPs to reduce holding costs
- Cost analysis and optimization recommendations
- Multi-region unified management
- Automated monitoring and alerting for idle resources
- Operation audit logging for compliance
Typical Use Cases:
- "Help me identify idle EIPs and generate an optimization report"
- "Generate an EIP cost analysis report to identify high-cost resources"
- "Set up idle EIP monitoring with automatic alerts via webhook or email"
- "View EIP distribution and status summary across all regions"
- "Show audit logs for EIP operations in the last 30 days"
- "List all EIPs in cn-north-4 with detailed information"
Prerequisites
1. Python Environment Requirements (MANDATORY)
- Python 3.8+
- Install SDKs:
pip install huaweicloudsdkeip huaweicloudsdkcore - All scripts use Python SDK v2 exclusively
Available Python Scripts:
scripts/analyze_idle_eips.py- Analyze idle EIPs and generate optimization reports (read-only, no release capability)scripts/monitor_idle_eips.py- Monitor idle EIPs with webhook/email alerts and cron supportscripts/eip_cost_report.py- Generate EIP cost analysis reports (HTML/JSON formats)scripts/list_eips.py- List all EIPs in a region (supports filtering and multi-region summary)
Available Shell Wrappers:
scripts/eip_audit_log.sh- Operation audit logging (standalone, no SDK dependency)
Note: All scripts are READ-ONLY. This skill does NOT perform bandwidth adjustment, tag management, or EIP release/deletion.
- Valid Huawei Cloud credentials (AK/SK mode)
- Security Rules:
- 🚫 Never expose AK/SK values in code, conversation, or commands
- 🚫 Never use
echo $HUAWEI_CLOUD_AKorecho $HUAWEI_CLOUD_SKto check credentials - ✅ Use environment variables:
HUAWEI_CLOUD_AK,HUAWEI_CLOUD_SK,HUAWEI_CLOUD_REGION - ✅ Prefer IAM users over root account for cloud operations
- ✅ Enable MFA for sensitive operations
Configuration Method (Environment Variables Only):
export HUAWEI_CLOUD_AK=<your-ak>
export HUAWEI_CLOUD_SK=<your-sk>
export HUAWEI_CLOUD_REGION=cn-north-4⚠️ Important Security Notes:
- Never commit credentials to version control
- Use IAM users with minimal required permissions
- Enable MFA for sensitive operations
- Rotate AK/SK regularly
2. IAM Permission Requirements
Note: This skill is READ-ONLY for EIP resources. It does NOT perform any write operations (update, delete, tag management).
| API Action | Permission | Purpose |
|---|---|---|
vpc:publicIps:list | List EIPs | Query all EIPs and their status |
vpc:publicIps:get | Get EIP details | View individual EIP information |
See IAM Permission Policies for complete policy JSON.
Permission Failure Handling:
1. When any command fails due to permission errors, read references/iam-policies.md 2. Display the required permission list and policy JSON to the user 3. Guide the user to create a custom policy in the IAM console and grant authorization 4. Pause execution and wait for user confirmation that permissions have been granted
Python SDK API Format Standard
All EIP operations use the Python SDK v2 format:
EIP Service API (v2 SDK)
from huaweicloudsdkeip.v2 import EipClient, ListPublicipsRequest, ShowPublicipRequest
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkcore.auth.credentials import BasicCredentials
# Initialize client
credentials = BasicCredentials(ak, sk)
client = EipClient(credentials, EipRegion.value_of(region))
# List all EIPs
request = ListPublicipsRequest()
request.limit = 100
response = client.list_publicips(request)
# Show EIP details
request = ShowPublicipRequest()
request.publicip_id = "<eip-id>"
response = client.show_publicip(request)Tag Management API
from huaweicloudsdkeip.v2 import UpdatePublicipRequest, UpdatePublicipOption, UpdatePublicipRequestBody
from huaweicloudsdkeip.v2.model.tag import Tag
# Add tags to EIP
option = UpdatePublicipOption()
option.tags = [Tag(key="env", value="prod")]
body = UpdatePublicipRequestBody()
body.publicip = option
request = UpdatePublicipRequest()
request.publicip_id = "<eip-id>"
request.body = body
client.update_publicip(request)Special Rules
| Rule | Description | Example |
|---|---|---|
| v2 SDK | EIP operations use v2 SDK | huaweicloudsdkeip.v2 |
| Region parameter | Use EipRegion.value_of(region) | EipRegion.value_of('cn-north-4') |
| Credentials | Use BasicCredentials(ak, sk) | Environment variables preferred |
Core Commands
EIP Query
Python SDK Method (Recommended):
# List all EIPs with bandwidth info
python3 scripts/adjust_eip_bandwidth.py --list
# Analyze idle EIPs and generate report (read-only)
python3 scripts/analyze_idle_eips.py
# Adjust bandwidth for all EIPs
python3 scripts/adjust_eip_bandwidth.py --all --bandwidth 5
# Adjust bandwidth for idle EIPs only
python3 scripts/adjust_eip_bandwidth.py --idle-only --bandwidth 12. jq Dependency (Shell Wrappers Only)
Required for: eip_audit_log.sh
# Verify jq installation
jq --version # Should return "jq-1.x"
# If jq fails or shows "Not Found", diagnose:
which jq
cat $(which jq) # If this shows text instead of binary, jq is broken
# Fix broken jq (common in WSL):
sudo rm /usr/local/bin/jq # Remove invalid PATH-prioritized jq
which jq # Should now show /usr/bin/jqNote: jq is only required for Shell wrapper scripts. All Python SDK scripts work without jq.
Python SDK Method (Recommended for batch operations):
# Adjust all EIPs to 5 Mbps
python3 scripts/adjust_eip_bandwidth.py --all --bandwidth 5
# Adjust idle EIPs to 1 Mbps
python3 scripts/adjust_eip_bandwidth.py --idle-only --bandwidth 1
# Adjust specific EIPs
python3 scripts/adjust_eip_bandwidth.py --eip-ids "eip-id1,eip-id2" --bandwidth 10
# View current bandwidth configuration
python3 scripts/adjust_eip_bandwidth.py --listTag Management
Python SDK Method:
# Add tags to EIP
python3 scripts/manage_tags.py --action add --tags "env=prod,team=backend" --eip-ids <ID1,ID2>
# Remove tags from EIP
python3 scripts/manage_tags.py --action remove --tags "env" --eip-ids <ID1,ID2>
# List tags for EIP
python3 scripts/list_eips.py --region cn-north-4 # Shows tags in outputMulti-Region Management
Using Python Scripts (Recommended):
# List EIPs in multiple regions (manually specify regions)
python3 scripts/list_eips.py --region cn-north-4
python3 scripts/list_eips.py --region cn-east-3
python3 scripts/list_eips.py --region cn-south-1
# Generate summary report for automation
python3 scripts/list_eips.py --region cn-north-4 --summary
# Output: CSV format (count,idle,bandwidth)Note: The multi_region_manage.sh wrapper was removed in v3.0.3. Users can achieve the same functionality by running list_eips.py for each region individually, or by scripting multiple calls in their own automation.
Operation Audit Logging
Using Audit Script:
# Log an EIP release operation
bash scripts/eip_audit_log.sh --action release --eip-id eip-xxx --operator admin
# Log a bandwidth adjustment
bash scripts/eip_audit_log.sh --action update_bandwidth --eip-id eip-xxx --details '{"old": 5, "new": 10}'
# Query audit logs for last 30 days
bash scripts/eip_audit_log.sh --query --days 30
# Export audit logs to CSV
bash scripts/eip_audit_log.sh --export --format csv
# Export to HTML report
bash scripts/eip_audit_log.sh --export --format htmlAudit Log Entry Format (JSONL):
{
"timestamp": "2026-05-25T03:30:00Z",
"operation": "release",
"eip_id": "eip-xxx",
"operator": "admin",
"region": "cn-north-4",
"details": {"reason": "idle_cleanup", "cost_saved": "2.5"}
}Parameter Confirmation
Python Script Parameters
| Parameter | Required/Optional | Description | Default |
|---|---|---|---|
--region | Optional (Python scripts) | Huawei Cloud region ID | HUAWEI_CLOUD_REGION or cn-north-4 |
--eip-ids | Optional (scripts) | Comma-separated EIP IDs | All EIPs |
--bandwidth | Required (adjust script) | Target bandwidth in Mbps | N/A |
--all | Optional | Apply to all EIPs | false |
--idle-only | Optional | Apply to idle EIPs only | false |
--list | Optional | List EIPs with bandwidth info | false |
--summary | Optional (list_eips.py) | Output CSV stats (count,idle,bandwidth) | false |
--idle-days | Optional (scripts) | Idle threshold in days | 7 |
--interactive | Optional (scripts) | Interactive confirmation mode | false |
--confirm | Optional (release script) | Confirm release operation | false |
Shell Wrapper Parameters
| Script | Parameter | Description |
|---|---|---|
eip_audit_log.sh | --action ACTION | Log operation (release, create, update) |
eip_audit_log.sh | --eip-id ID | EIP ID for audit log |
eip_audit_log.sh | --query | Query audit logs |
eip_audit_log.sh | --export --format csv | Export audit logs |
Output Format
EIP List Output (JSON)
{
"publicips": [
{
"id": "eip-xxx1",
"public_ip_address": "123.45.67.89",
"bandwidth": { "size": 5 },
"status": "ACTIVE",
"binding_status": "BOUND",
"associate_instance_type": "ECS",
"create_time": "2026-04-15T10:30:00Z"
}
]
}Script Output (Formatted Text)
========================================
Huawei Cloud EIP List (Region: cn-north-4)
========================================
EIP ID: eip-xxx1, IP: 123.45.67.89, BW: 5 Mbps, Status: BOUND (ECS: ecs-xxx)
EIP ID: eip-xxx2, IP: 98.76.54.32, BW: 10 Mbps, Status: UNBOUND ⚠️
========================================
Total: 2 EIPs, Idle: 1Cost Report Output (HTML)
Generated by scripts/eip_cost_report.py — includes statistics cards, idle EIP table, full EIP list, and optimization recommendations.
Verification
See Verification Method
Compliance Check Script
Before any skill update or release, run the automated compliance check:
# Run compliance check
python3 scripts/compliance_check.py
# Verbose mode (show all findings)
python3 scripts/compliance_check.py --verboseChecks performed:
1. All required Python SDK scripts exist 2. No hardcoded credentials (AK/SK) 3. No hardcoded local paths 4. SKILL.md metadata correctness
Exit codes:
0- All checks passed, skill is compliant1- Compliance issues found, must fix before release
Best Practices
1. Use Python SDK Scripts EXCLUSIVELY: Always use scripts/adjust_eip_bandwidth.py, scripts/analyze_idle_eips.py, and scripts/monitor_idle_eips.py 2. Regular Monitoring: Set up daily cron jobs with monitor_idle_eips.py --setup-cron to catch idle EIPs early 3. Tag Governance: Use consistent tags (env, team, project) for all EIPs 4. Bandwidth Policy: Set minimum bandwidth (1 Mbps) for idle EIPs to reduce costs before release 5. Interactive Mode: Always use --interactive flag for release operations in production 6. Audit Logging: Enable audit logging for all EIP operations using scripts/eip_audit_log.sh --action <operation> --eip-id <id> 7. Multi-Region Management: Run list_eips.py for each region individually, or use --summary flag for programmatic access to EIP statistics 8. Bandwidth Policy: Set minimum bandwidth (1 Mbps) for idle EIPs to reduce costs before release 9. Summary Mode for Automation: Use list_eips.py --summary for programmatic access to EIP statistics (returns CSV: count,idle,bandwidth) 10. Documentation Synchronization: When updating SKILL.md, ALWAYS update SKILL-CN.md simultaneously to maintain bilingual consistency. Both documents must have identical structure, parameters, and examples. 11. Shell Wrapper Audit: After any migration or script deletion, audit ALL Shell wrappers for broken dependencies using grep -r "<deleted-script>.sh" scripts/. Test each wrapper before marking compliance complete. 12. jq Dependency Validation: Before using audit log or multi-region scripts, verify jq is correctly installed: jq --version should return "jq-1.x". If it fails or shows "Not Found", check for broken PATH-prioritized installations at /usr/local/bin/jq and remove them. 13. Use Case Coverage Audit: Before any skill release, verify typical use cases cover 100% of scripts. Run: grep -A 20 "Typical Use Cases" SKILL.md | grep -c "^- \"" - should be 9+ for full coverage. Missing use cases indicate undocumented functionality.
Reference Documents
| Document | Description |
|---|---|
| IAM Permission Policies | Required permissions and policy JSON |
| EIP API Guide | EIP API reference (Python SDK v2) |
| Verification Method | Step-by-step verification |
| Python SDK Usage Guide | Python SDK patterns, common errors, and working examples. See "Issue 4" for critical bandwidth adjustment API pitfalls. |
Notes
- Cost estimates are for reference only — based on cn-north-4 on-demand pricing (~¥2-4/Mbps/month). Actual costs may vary by region and billing mode.
- This skill is READ-ONLY — it analyzes and reports idle EIPs but does NOT release or delete any resources. Manual action in the console is required to release EIPs.
- EIP release is irreversible — if you choose to release idle EIPs based on the analysis report, the public IP address will be reclaimed and cannot be recovered. Always verify before releasing manually.
- AK/SK must never be hardcoded — credentials should only be obtained via environment variables.
- Python SDK is the only supported method — all scripts use Python SDK v2 natively.
- Bandwidth adjustment API: Uses
BatchModifyBandwidthAPI (NOTUpdatePubliciporUpdateBandwidth).
Common Pitfalls
See Common Pitfalls & Solutions for detailed troubleshooting guides.
Quick Reference:
| Pitfall | Symptom | Quick Fix |
|---|---|---|
| jq path issues | eip_audit_log.sh fails | sudo rm /usr/local/bin/jq |
| Wrong bandwidth API | VPC.0301 error | Use BatchModifyBandwidthRequest |
| Missing bandwidth_id | Empty bandwidth ID | Access eip.bandwidth_size directly |
| v3 SDK import error | No EipRegion attribute | Use v2 SDK |
| Timestamp parsing | Unknown idle days | Handle ISO format |
#
Common Pitfalls & Solutions
This document contains detailed troubleshooting guides for common issues encountered when using the Huawei Cloud EIP Cost Optimizer skill.
Pitfall 1: Wrong API for Bandwidth Adjustment
Symptom: API returns error VPC.0301: updateBandwidth bandwidth params are invalid
Root Cause: Using UpdateBandwidthRequest or UpdatePublicipRequest instead of BatchModifyBandwidthRequest
Solution:
# ❌ WRONG - These APIs don't work for bandwidth adjustment
from huaweicloudsdkeip.v2 import UpdatePublicipRequest, UpdateBandwidthRequest
# ✅ CORRECT - Use BatchModifyBandwidthRequest
from huaweicloudsdkeip.v2 import BatchModifyBandwidthRequest, ModifyBandwidthOption, ModifyBandwidthRequestBody
bandwidth_option = ModifyBandwidthOption()
bandwidth_option.id = bandwidth_id # Required!
bandwidth_option.size = new_bandwidth_size
body = ModifyBandwidthRequestBody()
body.bandwidths = [bandwidth_option] # List format
request = BatchModifyBandwidthRequest()
request.body = body
response = client.batch_modify_bandwidth(request)Why this happens: The EIP v2 SDK has three bandwidth-related APIs:
1. update_publicip - Updates EIP basic properties (alias, IP version), NOT bandwidth 2. update_bandwidth - Exists but has parameter validation issues (SDK limitation) 3. batch_modify_bandwidth - ✅ Recommended, supports batch operations, correct parameter structure
Pitfall 2: Missing bandwidth_id in EIP List
EIP API Reference Guide
Overview
This document provides API reference information for Huawei Cloud Elastic Public IP (EIP) operations using Python SDK. All commands follow the standard format: Python SDK <SERVICE> <Operation> --param=value --cli-region=<region>.
Authentication
Method 1: Environment Variables
export HUAWEICLOUD_SDK_AK=<your-ak>
export HUAWEICLOUD_SDK_SK=<your-sk>Method 2: Python SDK Configuration
# Interactive configuration
Python SDK configure
# Verify configuration (safe - does not expose values)
Python SDK configure list✅ Correct: Use Python SDK configure list to verify credentials ❌ Incorrect: Never use echo $HUAWEICLOUD_SDK_AK to check credentials
EIP Commands (v3 API)
1. List EIPs
Python SDK EIP ListPublicips/v3 --cli-region=cn-north-4Parameters:
--cli-region(required): Region ID
Response Example:
{
"publicips": [
{
"id": "eip-xxx1",
"public_ip_address": "123.45.67.89",
"bandwidth": { "size": 5, "name": "bw-001" },
"status": "ACTIVE",
"binding_status": "BOUND",
"associate_instance_type": "ECS",
"create_time": "2026-04-15T10:30:00Z"
},
{
"id": "eip-xxx2",
"public_ip_address": "98.76.54.32",
"bandwidth": { "size": 10, "name": "bw-002" },
"status": "ACTIVE",
"binding_status": "UNBOUND",
"create_time": "2026-03-20T14:20:00Z"
}
]
}2. Show EIP Details
Python SDK EIP ShowPublicip/v3 --publicip_id=<eip-id> --cli-region=cn-north-4Parameters:
--publicip_id(required): EIP ID--cli-region(required): Region ID
3. Create EIP
Python SDK EIP CreatePublicip/v3 \
--publicip.type=EIP \
--publicip.bandwidth.name=bw-001 \
--publicip.bandwidth.size=5 \
--cli-region=cn-north-4Parameters:
--publicip.type(required): AlwaysEIP--publicip.bandwidth.name(required): Bandwidth name--publicip.bandwidth.size(required): Bandwidth in Mbps--cli-region(required): Region ID
4. Delete EIP (Irreversible!)
Python SDK EIP DeletePublicip/v3 --publicip_id=<eip-id> --cli-region=cn-north-4⚠️ Warning: This operation is irreversible. The public IP address will be reclaimed.
5. Update EIP Bandwidth
Python SDK EIP UpdatePublicip/v3 \
--publicip_id=<eip-id> \
--publicip.bandwidth.size=10 \
--cli-region=cn-north-4Parameters:
--publicip_id(required): EIP ID--publicip.bandwidth.size(required): New bandwidth in Mbps--cli-region(required): Region ID
6. Associate EIP to Resource
Python SDK EIP AssociatePublicip/v3 \
--publicip_id=<eip-id> \
--associate_instance_type=ECS \
--associate_instance_id=<instance-id> \
--cli-region=cn-north-47. Disassociate EIP from Resource
Python SDK EIP DisassociatePublicip/v3 \
--publicip_id=<eip-id> \
--cli-region=cn-north-4Tag Management Commands
Create Tags
Python SDK EIP CreatePublicipTags/v3 \
--publicip_id=<eip-id> \
--tag.1.key=env \
--tag.1.value=prod \
--tag.2.key=team \
--tag.2.value=devops \
--cli-region=cn-north-4Delete Tags
Python SDK EIP DeletePublicipTags/v3 \
--publicip_id=<eip-id> \
--tag.1.key=env \
--cli-region=cn-north-4List Tags
Python SDK EIP ShowPublicipTags/v3 \
--publicip_id=<eip-id> \
--cli-region=cn-north-4Common Region IDs
| Region Name | Region ID |
|---|---|
| North China - Beijing 4 | cn-north-4 |
| North China - Beijing 1 | cn-north-1 |
| East China - Shanghai 1 | cn-east-3 |
| South China - Guangzhou | cn-south-1 |
| Asia Pacific - Hong Kong | ap-southeast-1 |
| Asia Pacific - Singapore | ap-southeast-2 |
| Europe - Paris | eu-west-0 |
EIP Status Reference
| Status | Description |
|---|---|
ACTIVE | Running normally |
DOWN | Deactivated |
ERROR | Error state |
FREEZED | Frozen |
BIND_ERROR | Bind failed |
ELB_DELETING | ELB deleting |
BINDING | Binding in progress |
UNBINDING | Unbinding in progress |
Binding Status Reference
| Binding Status | Description |
|---|---|
BOUND | Bound to a resource |
UNBOUND | Not bound (idle) |
Cost Estimation Reference
⚠️ Important: Prices are for reference only. Actual costs may vary by region and billing mode.
On-Demand Pricing (North China - Beijing 4)
| Bandwidth (Mbps) | Monthly Cost (CNY) |
|---|---|
| 1 | ¥2.00 |
| 5 | ¥10.00 |
| 10 | ¥20.00 |
| 20 | ¥40.00 |
| 50 | ¥100.00 |
Formula
Monthly Cost = Bandwidth (Mbps) × Unit Price (CNY/Mbps/Month)Unit price varies by region, typically ¥2-4/Mbps/month.
Best Practices
1. Regular Cleanup: Schedule weekly idle EIP scans 2. Tag Governance: Use consistent tags (env, team, project) for all EIPs 3. Bandwidth Policy: Set minimum bandwidth for idle EIPs 4. Always Confirm Before Release: Use --interactive mode in production
Common Errors
| Error | Cause | Solution |
|---|---|---|
InvalidAccessKeyId | Invalid AK/SK | Check credential configuration via Python SDK configure list |
EipNotFound | EIP does not exist | Verify EIP ID is correct |
EipIsBound | EIP is bound to a resource | Disassociate first, then release |
BandwidthOutOfRange | Bandwidth out of range | Valid range: 1-2000 Mbps |
RequestLimitExceeded | Too many requests | Add delay between requests |
Related Documentation
IAM Permission Policies - EIP Management Skill
Overview
This document declares the IAM permissions required by the Huawei Cloud EIP Batch Management & Cost Optimization skill. All permissions follow the principle of least privilege.
Basic Operations (Read-Only)
| API Action | Permission | Purpose |
|---|---|---|
vpc:publicIps:list | List EIPs | Query all EIPs and their binding status |
vpc:publicIps:get | Get EIP details | View individual EIP information |
vpc:publicIpTags:list | List tags | Query tags on EIPs |
Write Operations (Require Additional Authorization)
| API Action | Permission | Purpose |
|---|---|---|
vpc:publicIps:create | Create EIP | Create new Elastic Public IPs |
vpc:publicIps:delete | Delete EIP | Release idle EIPs (irreversible) |
vpc:publicIps:update | Update EIP | Adjust bandwidth size |
vpc:publicIps:associate | Associate EIP | Bind EIP to a resource (ECS, ELB, NAT) |
vpc:publicIps:disassociate | Disassociate EIP | Unbind EIP from a resource |
vpc:publicIpTags:create | Create tags | Add tags to EIPs |
vpc:publicIpTags:delete | Delete tags | Remove tags from EIPs |
Monitoring Operations (Optional)
| API Action | Permission | Purpose |
|---|---|---|
ces:metrics:get | Get metrics | Query EIP bandwidth usage metrics |
ces:alarms:list | List alarms | View existing alarm rules |
Minimum Read-Only Policy (JSON)
{
"Version": "1.1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"vpc:publicIps:list",
"vpc:publicIps:get",
"vpc:publicIpTags:list"
],
"Resource": ["*"]
}
]
}Full Management Policy (JSON)
{
"Version": "1.1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"vpc:publicIps:list",
"vpc:publicIps:get",
"vpc:publicIps:create",
"vpc:publicIps:delete",
"vpc:publicIps:update",
"vpc:publicIps:associate",
"vpc:publicIps:disassociate",
"vpc:publicIpTags:list",
"vpc:publicIpTags:create",
"vpc:publicIpTags:delete"
],
"Resource": ["*"]
}
]
}Permission Assignment Steps
1. Log in to Huawei Cloud IAM console: https://console.huaweicloud.com/iam/ 2. Navigate to Policies → Create Custom Policy 3. Choose JSON mode and paste the policy JSON above 4. Navigate to Users / User Groups → Authorize 5. Select the custom policy and confirm
Permission Failure Handling
When a command fails with a permission error:
1. Read this document (references/iam-policies.md) 2. Display the required permission list and policy JSON to the user 3. Guide the user to create a custom policy in the IAM console 4. Pause execution and wait for user confirmation that permissions have been granted 5. Retry the failed command
闲置 EIP 监控工具使用指南
功能概述
monitor_idle_eips.py 是一个基于华为云 Python SDK v2 的闲置 EIP 监控工具,支持:
- ✅ 自动扫描闲置 EIP(未绑定的 EIP)
- ✅ 自定义闲置天数阈值
- ✅ 企业微信/钉钉 webhook 通知
- ✅ 邮件告警通知
- ✅ 定时监控任务(cron)
- ✅ 详细的监控报告(包含成本估算)
快速开始
1. 配置环境变量
export HUAWEI_CLOUD_AK='your-access-key'
export HUAWEI_CLOUD_SK='your-secret-key'
export HUAWEI_CLOUD_REGION='cn-north-4' # 可选,默认 cn-north-42. 基本扫描
# 扫描闲置 EIP(默认阈值:7 天)
python3 scripts/monitor_idle_eips.py --scan
# 自定义闲置天数阈值
python3 scripts/monitor_idle_eips.py --scan --idle-days 33. 发送告警通知
企业微信/钉钉 webhook
# 企业微信群机器人 webhook
python3 scripts/monitor_idle_eips.py --scan \
--wechat-webhook "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
# 钉钉群机器人 webhook
python3 scripts/monitor_idle_eips.py --scan \
--wechat-webhook "https://oapi.dingtalk.com/robot/send?access_token=xxx"邮件告警
python3 scripts/monitor_idle_eips.py --scan \
--email "admin@example.com" \
--email-user "sender@163.com" \
--email-pass "your-authorization-code" \
--email-smtp "smtp.163.com" \
--email-port 4654. 设置定时监控
# 设置每天 9:00 自动扫描
python3 scripts/monitor_idle_eips.py --setup-cron
# 设置定时任务并配置 webhook
python3 scripts/monitor_idle_eips.py --setup-cron \
--wechat-webhook "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"输出示例
控制台报告
====================================================================================================
⚠️ 闲置 EIP 监控报告 (区域:cn-north-4)
====================================================================================================
扫描时间:2026-05-25 02:07:15
发现闲置 EIP 数量:1 个
====================================================================================================
EIP ID IP 地址 带宽 状态 闲置天数 创建时间
----------------------------------------------------------------------------------------------------
9287783b-12c3-4288-bafd-22d91288f99c 120.46.5.112 1 Mbps DOWN 未知 2026-05-24 17:24:59
----------------------------------------------------------------------------------------------------
💰 估算带宽资源浪费:1 Mbps
⚠️ 建议及时释放或绑定这些闲置 EIP 以节省成本
====================================================================================================企业微信消息
## ⚠️ 闲置 EIP 告警 - 发现 1 个闲置 EIP
**扫描时间**: 2026-05-25 02:07:15
### 📊 发现 1 个闲置 EIP
| EIP ID | IP 地址 | 带宽 | 闲置天数 |
|--------|---------|------|----------|
| 9287783b... | 120.46.5.112 | 1 Mbps | 未知 |
建议及时处理闲置 EIP 以节省成本。参数说明
| 参数 | 说明 | 默认值 | 示例 |
|---|---|---|---|
--scan | 扫描闲置 EIP(必需) | - | --scan |
--idle-days DAYS | 闲置天数阈值 | 7 | --idle-days 3 |
--wechat-webhook URL | 企业微信/钉钉 webhook URL | - | --wechat-webhook "https://..." |
--email EMAIL | 告警邮箱地址 | - | --email "admin@example.com" |
--email-smtp HOST | SMTP 服务器 | smtp.163.com | --email-smtp "smtp.qq.com" |
--email-port PORT | SMTP 端口 | 465 | --email-port 587 |
--email-user USER | SMTP 登录用户名 | - | --email-user "sender@163.com" |
--email-pass PASS | SMTP 登录密码/授权码 | - | --email-pass "abc123" |
--setup-cron | 设置定时监控任务 | - | --setup-cron |
闲置 EIP 判定规则
一个 EIP 被判定为闲置需要满足以下条件:
1. 未绑定资源: port_id 为 None 或空字符串 2. 超过闲置阈值: 创建时间距离当前时间 >= --idle-days 指定的天数
注意: 如果无法解析 EIP 的创建时间,只要 EIP 未绑定,就会被视为闲置。
成本估算
工具会根据闲置 EIP 的带宽大小估算资源浪费:
- 带宽浪费: 所有闲置 EIP 的带宽总和(Mbps)
- 费用参考: 华为云 EIP 带宽费用约为 0.8 元/Mbps/天(按量计费)
例如:1 个闲置 EIP,带宽 5 Mbps,每天浪费约 4 元。
最佳实践
1. 每日定时监控
# 设置每天早上 9:00 自动扫描并发送微信通知
python3 scripts/monitor_idle_eips.py --setup-cron \
--wechat-webhook "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \
--idle-days 72. 结合闲置 EIP 释放
# 先扫描监控
python3 scripts/monitor_idle_eips.py --scan --idle-days 7
# 确认无误后释放闲置 EIP
python3 scripts/find_and_release_idle_eips.py --release3. 多区域监控
# 为不同区域设置独立的监控任务
export HUAWEI_CLOUD_REGION=cn-north-4
python3 scripts/monitor_idle_eips.py --setup-cron --idle-days 7
export HUAWEI_CLOUD_REGION=cn-east-3
python3 scripts/monitor_idle_eips.py --setup-cron --idle-days 7故障排查
问题 1: 认证失败
❌ 错误:未检测到认证信息
请设置环境变量 HUAWEI_CLOUD_AK 和 HUAWEI_CLOUD_SK解决方案: 确保已正确设置环境变量:
export HUAWEI_CLOUD_AK='your-ak'
export HUAWEI_CLOUD_SK='your-sk'问题 2: webhook 发送失败
❌ 发送微信通知失败:HTTP Error 400: Bad Request解决方案: 1. 检查 webhook URL 是否正确 2. 确认机器人已添加到群聊 3. 检查 webhook 是否已启用
问题 3: 邮件发送失败
❌ 发送邮件告警失败:(535, b'Authentication Failed')解决方案: 1. 使用授权码而非登录密码(163/QQ 邮箱) 2. 确认 SMTP 服务已开启 3. 检查邮箱账号和密码是否正确
相关文件
- 脚本路径:
scripts/monitor_idle_eips.py - 技能文档:
SKILL.md - 参考文档:
references/python-sdk-usage-guide.md
版本历史
- v1.0.0 (2026-05-25): 初始版本,支持扫描、webhook、邮件、定时任务
Huawei Cloud EIP Python SDK Usage Guide
Overview
This document covers Python SDK usage patterns for Huawei Cloud EIP operations, based on real-world testing and troubleshooting sessions.
SDK Installation
pip install huaweicloudsdkeip huaweicloudsdkvpcNote: SDK version 3.1.196+ tested and working.
Authentication
from huaweicloudsdkcore.auth.credentials import BasicCredentials
ak = os.environ.get('HUAWEI_CLOUD_AK')
sk = os.environ.get('HUAWEI_CLOUD_SK')
region = os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')
credentials = BasicCredentials(ak=ak, sk=sk)Security Rules:
- ✅ Use environment variables for AK/SK
- ✅ Use IAM users instead of root account
- ❌ Never hardcode credentials in scripts
- ❌ Never print full AK/SK values (use slicing:
ak[:8]...ak[-4:])
Client Initialization
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkeip.v2.eip_client import EipClient
client = EipClient.new_builder() \
.with_credentials(credentials) \
.with_region(EipRegion.value_of(region)) \
.build()Common Operations
1. List All EIPs
from huaweicloudsdkeip.v2.model.list_publicips_request import ListPublicipsRequest
request = ListPublicipsRequest()
response = client.list_publicips(request)
eips = response.publicips
for eip in eips:
print(f"{eip.id}: {eip.public_ip_address} (status: {eip.status}, bound: {eip.port_id is not None})")2. List All Bandwidths
from huaweicloudsdkeip.v2.model.list_bandwidths_request import ListBandwidthsRequest
request = ListBandwidthsRequest()
response = client.list_bandwidths(request)
bandwidths = response.bandwidths
bw_map = {bw.id: bw for bw in bandwidths}3. Batch Adjust Bandwidth
Important: The API structure is:
BatchModifyBandwidthRequesttakesModifyBandwidthRequestBodyModifyBandwidthRequestBodycontainsbandwidthslist- Each item is
ModifyBandwidthOption(id=eip_id, size=bandwidth_mbps)
from huaweicloudsdkeip.v2.model.batch_modify_bandwidth_request import BatchModifyBandwidthRequest
from huaweicloudsdkeip.v2.model.modify_bandwidth_request_body import ModifyBandwidthRequestBody
from huaweicloudsdkeip.v2.model.modify_bandwidth_option import ModifyBandwidthOption
eip_ids = ['eip-id-1', 'eip-id-2']
bandwidth_size = 5 # Mbps
# Create option for each EIP
bandwidth_options = [
ModifyBandwidthOption(id=eip_id, size=bandwidth_size)
for eip_id in eip_ids
]
body = ModifyBandwidthRequestBody(bandwidths=bandwidth_options)
request = BatchModifyBandwidthRequest(body=body)
response = client.batch_modify_bandwidth(request)4. Release EIP
from huaweicloudsdkeip.v2.model.delete_publicip_request import DeletePublicipRequest
eip_id = 'eip-id-to-delete'
request = DeletePublicipRequest(publicip_id=eip_id)
response = client.delete_publicip(request)Error Handling
from huaweicloudsdkcore.exceptions.exceptions import ServiceResponseException
try:
request = ListPublicipsRequest()
response = client.list_publicips(request)
except ServiceResponseException as e:
print(f"Error: {e}")
print(f"Status code: {e.status_code}")
print(f"Error message: {e.error_msg}")Common Import Errors & Solutions
Issue 1: ModuleNotFoundError for list_publicip_bandwidths_request
Wrong:
from huaweicloudsdkeip.v2.model.list_publicip_bandwidths_request import ListPublicipBandwidthsRequestCorrect:
from huaweicloudsdkeip.v2.model.list_bandwidths_request import ListBandwidthsRequestIssue 2: ModuleNotFoundError for batch_modify_bandwidth_request_body
Wrong:
from huaweicloudsdkeip.v2.model.batch_modify_bandwidth_request_body import BatchModifyBandwidthRequestBodyCorrect:
from huaweicloudsdkeip.v2.model.modify_bandwidth_request_body import ModifyBandwidthRequestBodyIssue 3: ServiceException import error
Wrong:
from huaweicloudsdkcore.exceptions.service_exception import ServiceExceptionCorrect:
from huaweicloudsdkcore.exceptions.exceptions import ServiceResponseExceptionIssue 4: Using wrong API for bandwidth adjustment (CRITICAL)
Common Mistake: Trying to use UpdatePublicip or UpdateBandwidth APIs
Wrong Approach 1 - UpdatePublicip:
from huaweicloudsdkeip.v2 import UpdatePublicipRequest, UpdatePublicipOption
publicip_option = UpdatePublicipOption()
publicip_option.bandwidth_size = 5 # ❌ This parameter doesn't exist!Wrong Approach 2 - UpdateBandwidth:
from huaweicloudsdkeip.v2 import UpdateBandwidthRequest, UpdateBandwidthOption
bandwidth_option = UpdateBandwidthOption()
bandwidth_option.size = 5
request = UpdateBandwidthRequest(bandwidth_id=bw_id, body=bandwidth_option)
response = client.update_bandwidth(request) # ❌ Returns 400 VPC.0301 errorCorrect Approach - BatchModifyBandwidth:
from huaweicloudsdkeip.v2 import (
BatchModifyBandwidthRequest,
ModifyBandwidthRequestBody,
ModifyBandwidthOption
)
# Step 1: Create option with id and size
bandwidth_option = ModifyBandwidthOption()
bandwidth_option.id = '5466e72a-9f4e-4b7a-aea8-8d9cd1739458' # bandwidth_id
bandwidth_option.size = 5 # new bandwidth in Mbps
# Step 2: Wrap in request body (bandwidths is a list)
body = ModifyBandwidthRequestBody()
body.bandwidths = [bandwidth_option]
# Step 3: Create and execute request
request = BatchModifyBandwidthRequest()
request.body = body
response = client.batch_modify_bandwidth(request) # ✅ SuccessError Message when using wrong API:
ClientRequestException: {status_code:400, error_code:VPC.0301,
error_msg:updateBandwidth bandwidth params are invalid.}Why this happens: The SDK has multiple bandwidth-related APIs:
UpdateBandwidth- for updating bandwidth billing mode (not size)UpdatePublicip- for updating EIP attributes (not bandwidth)BatchModifyBandwidth- ✅ The ONLY API that adjusts bandwidth size
Pitfall: This is not obvious from the SDK documentation. Always use batch_modify_bandwidth for bandwidth size adjustments.
API Discovery Pattern
When unsure about API structure, use this pattern:
import sys
sys.path.insert(0, '/path/to/site-packages')
from huaweicloudsdkeip.v2 import model
# Find APIs by keyword
items = [x for x in dir(model) if 'bandwidth' in x.lower() and 'modify' in x.lower()]
for item in items:
print(f' - {item}')
# Inspect request structure
from huaweicloudsdkeip.v2.model.batch_modify_bandwidth_request import BatchModifyBandwidthRequest
req = BatchModifyBandwidthRequest()
print(req.openapi_types) # Shows expected body type
print(req.attribute_map)Idle EIP Detection
Reliable Method: Check if port_id is None
idle_eips = [eip for eip in eips if not eip.port_id]Unreliable Methods (avoid):
binding_statusfield may not exist in all API versionsbind_typefield may be empty string instead ofNoneassociate_instance_typemay be present even for idle EIPs
Complete Working Example
See scripts/adjust_eip_bandwidth.py for a complete, production-ready example that:
- Lists all EIPs with bandwidth information
- Supports
--list,--all,--idle-only,--eip-idsmodes - Interactive confirmation before destructive operations
- Proper error handling and user feedback
- Statistics and summary output
Region Codes
| Region | Code |
|---|---|
| 华北 - 北京四 | cn-north-4 |
| 华北 - 北京一 | cn-north-1 |
| 华东 - 上海一 | cn-east-3 |
| 华东 - 上海二 | cn-east-2 |
| 华南 - 广州 | cn-south-1 |
| 华南 - 深圳 | cn-south-4 |
| 西南 - 贵阳一 | cn-southwest-2 |
| 亚太 - 曼谷 | ap-southeast-2 |
| 亚太 - 新加坡 | ap-southeast-1 |
| 亚太 - 香港 | ap-southeast-3 |
Troubleshooting
Issue: "No module named 'huaweicloudsdkeip'"
Solution: Install SDK with --break-system-packages flag (WSL/Ubuntu):
pip install huaweicloudsdkeip huaweicloudsdkvpc --break-system-packagesOr use virtual environment:
python3 -m venv venv
source venv/bin/activate
pip install huaweicloudsdkeip huaweicloudsdkvpcIssue: "externally-managed-environment" error
This is a PEP 668 restriction in modern Python distributions.
Solutions: 1. Use --break-system-packages flag (quick fix) 2. Use virtual environment (recommended for development) 3. Use pipx for application installation
Issue: Empty EIP list returned
Possible causes: 1. Wrong region - verify with HUAWEI_CLOUD_REGION 2. No EIPs in that region (confirmed empty) 3. Permission issue - check IAM policies 4. Credential issue - verify AK/SK
Debug:
print(f"Region: {region}")
print(f"AK: {ak[:8]}...{ak[-4:]}")Session Notes
Date: 2026-05-25 Region: cn-north-4 Result: Successfully identified and released 1 idle EIP (120.46.5.112) Follow-up: No EIPs remaining in cn-north-4 region
Key Learnings: 1. Shell scripts in this skill depend heavily on Python SDK (Python SDK command) 2. Python SDK scripts provide better portability 3. API naming in SDK v3.1.196 differs from documentation (e.g., ListBandwidthsRequest vs ListPublicipBandwidthsRequest) 4. Always test imports before writing full scripts
Verification Method - EIP Management Skill
Overview
This document defines the verification steps for the EIP management skill. Verification is divided into three levels: installation verification, configuration verification, and functional verification.
Level 1: Installation Verification
1.1 Python SDK Installation
| Item | Command | Success Criteria |
|---|---|---|
| Python SDK installed | Python SDK version | Returns version number >= 7.2.2 |
| jq installed | jq --version | Returns version number (e.g., jq-1.6) |
| Python 3 installed | python3 --version | Returns version >= 3.6 |
1.2 Python SDK First Run
# Accept privacy statement (first time only)
printf "y\n" | Python SDK versionExpected: Version number displayed without error.
Level 2: Configuration Verification
2.1 Credential Configuration
| Item | Command | Success Criteria |
|---|---|---|
| Credentials configured | export HUAWEI_CLOUD_AK/SK list | Shows valid AK/SK configuration (values masked) |
| Region configured | export HUAWEI_CLOUD_AK/SK list | Shows cli-region setting |
✅ Correct: Use export HUAWEI_CLOUD_AK/SK list to verify ❌ Incorrect: Do NOT use echo $HUAWEICLOUD_SDK_AK to check credentials
2.2 Connectivity Test
# Test API connectivity with a read-only operation
Python SDK EIP ListPublicips/v3 --cli-region=cn-north-4Expected: Returns HTTP 200 and EIP list (may be empty).
Level 3: Functional Verification
3.1 List EIPs
bash scripts/list_eips.shExpected: Displays formatted EIP list with total and idle counts.
3.2 Find Idle EIPs
bash scripts/find_idle_eips.shExpected: Displays idle EIP report with cost estimates.
3.3 Cost Report Generation
python3 scripts/eip_cost_report.py --output /tmp/test-report.htmlExpected: HTML report generated at /tmp/test-report.html.
3.4 Multi-Region Management
bash scripts/multi_region_manage.sh --regions "cn-north-4"Expected: Displays EIP statistics for the specified region.
3.5 Audit Log (Query Mode)
bash scripts/eip_audit_log.sh --query --days 7Expected: Displays audit log entries from the last 7 days (may be empty).
3.6 Tag Management (Read-Only)
# List tags on an EIP (replace with a real EIP ID)
Python SDK EIP ShowPublicipTags/v3 --publicip_id=<eip-id> --cli-region=cn-north-4Expected: Returns tag information for the specified EIP.
Verification Checklist
| # | Check Item | Command | Status |
|---|---|---|---|
| 1 | Python SDK version >= 7.2.2 | Python SDK version | ☐ |
| 2 | jq installed | jq --version | ☐ |
| 3 | Python 3.6+ installed | python3 --version | ☐ |
| 4 | Credentials configured | export HUAWEI_CLOUD_AK/SK list | ☐ |
| 5 | API connectivity | Python SDK EIP ListPublicips/v3 --cli-region=cn-north-4 | ☐ |
| 6 | List EIPs script | bash scripts/list_eips.sh | ☐ |
| 7 | Find idle EIPs script | bash scripts/find_idle_eips.sh | ☐ |
| 8 | Cost report generation | python3 scripts/eip_cost_report.py --output /tmp/test-report.html | ☐ |
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
查找闲置 EIP 并生成分析报告(仅分析,不执行释放操作)
Usage: python3 analyze_idle_eips.py [--region cn-north-4] [--min-idle-days 7]
"""
import os
import sys
import json
import argparse
from datetime import datetime
from huaweicloudsdkeip.v2 import EipClient, ListPublicipsRequest
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkcore.auth.credentials import BasicCredentials
def get_credentials():
"""从环境变量获取 AK/SK"""
ak = os.environ.get('HUAWEI_CLOUD_AK')
sk = os.environ.get('HUAWEI_CLOUD_SK')
region = os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')
if not ak or not sk:
print("❌ 错误:未找到华为云凭证")
print("请设置环境变量:")
print(" export HUAWEI_CLOUD_AK=<your-ak>")
print(" export HUAWEI_CLOUD_SK=<your-sk>")
print(" export HUAWEI_CLOUD_REGION=cn-north-4 # 可选,默认 cn-north-4")
sys.exit(1)
return BasicCredentials(ak=ak, sk=sk), region
def list_eips(client, region):
"""列出所有 EIP"""
request = ListPublicipsRequest()
request.limit = 100
try:
response = client.list_publicips(request)
return response.publicips if response.publicips else []
except Exception as e:
print(f"❌ 查询 EIP 失败:{e}")
return []
def find_idle_eips(eips, min_idle_days=0):
"""找出闲置 EIP(未绑定的)"""
idle_eips = []
for eip in eips:
# 检查绑定状态:port_id 为 None 或空表示未绑定
port_id = getattr(eip, 'port_id', None)
status = getattr(eip, 'status', 'UNKNOWN')
# 未绑定的 EIP (port_id 为 None 或空字符串)
if port_id is None or port_id == '':
# 计算闲置天数
create_time = getattr(eip, 'create_time', None)
idle_days = calculate_idle_days(create_time) if create_time else -1
# 只有闲置天数 >= 阈值才加入列表
if idle_days >= 0 and idle_days >= min_idle_days:
idle_eips.append(eip)
return idle_eips
def calculate_idle_days(create_time_str):
"""计算闲置天数(从创建时间开始)"""
try:
# 解析 ISO 格式时间:2024-01-15T10:30:00Z
if 'T' in create_time_str:
create_time = datetime.fromisoformat(create_time_str.replace('Z', '+00:00'))
else:
create_time = datetime.strptime(create_time_str, '%Y-%m-%d %H:%M:%S')
now = datetime.now(create_time.tzinfo) if create_time.tzinfo else datetime.now()
delta = now - create_time
return delta.days
except Exception:
return -1 # 无法计算
def print_eip_table(eips, title="EIP 列表", min_idle_days=0):
"""打印 EIP 表格"""
print("\n" + "=" * 120)
print(f"{title} (区域:{os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')}, 闲置阈值:≥{min_idle_days}天)")
print("=" * 120)
print(f"{'EIP ID':<40} {'IP 地址':<16} {'带宽 ID':<20} {'状态':<10} {'端口绑定':<12} {'创建时间':<20} {'闲置天数'}")
print("-" * 120)
for eip in eips:
eip_id = getattr(eip, 'id', 'N/A')[:36]
ip = getattr(eip, 'public_ip_address', 'N/A')
bw_id = getattr(eip, 'bandwidth_id', 'N/A')[:16] if getattr(eip, 'bandwidth_id', None) else 'N/A'
status = getattr(eip, 'status', 'N/A')
port_id = getattr(eip, 'port_id', 'N/A')
create_time = getattr(eip, 'create_time', 'N/A')[:19] if getattr(eip, 'create_time', None) else 'N/A'
# 闲置 EIP 高亮标记
is_idle = port_id is None or port_id == ''
marker = " ⚠️ 闲置" if is_idle else ""
port_status = "未绑定" if is_idle else f"已绑定 ({port_id[:8]}...)"
# 计算闲置天数
idle_days = calculate_idle_days(create_time) if is_idle else 0
idle_days_str = f"{idle_days} 天" if idle_days >= 0 else "未知"
print(f"{eip_id:<40} {ip:<16} {bw_id:<20} {status:<10} {port_status:<12} {create_time:<20} {idle_days_str}{marker}")
print("-" * 120)
idle_count = len([e for e in eips if (getattr(e, 'port_id', None) is None or getattr(e, 'port_id', '') == '') and calculate_idle_days(getattr(e, 'create_time', None)) >= min_idle_days])
print(f"总计:{len(eips)} 个 EIP, 闲置:{idle_count} 个 (阈值:≥{min_idle_days}天)")
print("=" * 120 + "\n")
def generate_report(all_eips, idle_eips, min_idle_days=0):
"""生成分析报告"""
print("\n" + "=" * 120)
print(f"📊 闲置 EIP 分析报告 (闲置阈值:≥{min_idle_days}天)")
print("=" * 120)
# 成本估算(按 cn-north-4 按需价格)
# 参考:EIP 保留费约 ¥0.02/小时/个,带宽费按实际带宽计算
estimated_hourly_cost = len(idle_eips) * 0.02 # 仅保留费
estimated_monthly_cost = estimated_hourly_cost * 24 * 30
print(f"\n📈 资源概览:")
print(f" - EIP 总数:{len(all_eips)} 个")
print(f" - 闲置 EIP 数量:{len(idle_eips)} 个")
print(f" - 闲置率:{len(idle_eips)/len(all_eips)*100:.1f}%" if all_eips else " - 闲置率:N/A")
print(f"\n💰 成本估算(参考 cn-north-4 按需价格):")
print(f" - 当前每小时成本:约 ¥{estimated_hourly_cost:.2f}/小时")
print(f" - 当前每月成本:约 ¥{estimated_monthly_cost:.2f}/月")
print(f" - 潜在节省:100%(如释放所有闲置 EIP)")
print(f"\n⚠️ 风险提示:")
print(f" - EIP 释放是不可逆操作,IP 地址将被回收且无法恢复")
print(f" - 建议先确认闲置 EIP 是否用于备用、灾备或临时业务")
print(f" - 释放前请通知相关业务负责人")
print(f"\n💡 优化建议:")
if len(idle_eips) == 0:
print(" ✅ 所有 EIP 都在使用中,无需优化")
else:
print(" 1. 【立即行动】确认闲置 EIP 的业务用途,联系负责人核实")
print(" 2. 【降低成本】对确认不用的 EIP,手动在控制台释放")
print(" 3. 【保留备用】对可能需要但暂时闲置的 EIP,可调整为最低带宽(1 Mbps)")
print(" 4. 【定期审计】建议每周运行此脚本,持续监控闲置资源")
print(" 5. 【标签管理】为 EIP 添加用途标签,便于后续识别和管理")
# 详细清单
if idle_eips:
print(f"\n📋 闲置 EIP 详细清单:")
for i, eip in enumerate(idle_eips, 1):
eip_id = getattr(eip, 'id', 'N/A')
ip = getattr(eip, 'public_ip_address', 'N/A')
create_time = getattr(eip, 'create_time', 'N/A')[:19] if getattr(eip, 'create_time', None) else 'N/A'
idle_days = calculate_idle_days(create_time)
idle_days_str = f"{idle_days} 天" if idle_days >= 0 else "未知"
print(f"\n [{i}] EIP: {ip}")
print(f" ID: {eip_id}")
print(f" 创建时间:{create_time}")
print(f" 闲置时长:{idle_days_str}")
print(f" 建议操作:{'立即释放' if idle_days > 30 else '确认用途后决定'}")
print("\n" + "=" * 120)
print("📝 报告生成时间:", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print("=" * 120 + "\n")
def main():
# 解析命令行参数
parser = argparse.ArgumentParser(description='查找闲置 EIP 并生成分析报告')
parser.add_argument('--region', type=str, default=None, help='华为云区域 ID (默认从环境变量读取)')
parser.add_argument('--min-idle-days', type=int, default=0, help='最小闲置天数阈值 (默认:0,即所有未绑定 EIP)')
args = parser.parse_args()
# 获取凭证
credentials, region_id = get_credentials()
# 命令行参数优先于环境变量
if args.region:
region_id = args.region
min_idle_days = args.min_idle_days
# 创建客户端
client = EipClient.new_builder() \
.with_credentials(credentials) \
.with_region(EipRegion.value_of(region_id)) \
.build()
print(f"🔍 正在查询区域 {region_id} 的 EIP... (闲置阈值:≥{min_idle_days}天)")
# 查询所有 EIP
all_eips = list_eips(client, region_id)
if not all_eips:
print("ℹ️ 未找到任何 EIP")
return
# 打印所有 EIP
print_eip_table(all_eips, "所有 EIP", min_idle_days)
# 找出闲置 EIP(根据阈值过滤)
idle_eips = find_idle_eips(all_eips, min_idle_days)
if not idle_eips:
print(f"✅ 未发现闲置超过 {min_idle_days} 天的 EIP,所有 EIP 都在使用中或闲置时间不足")
return
# 打印闲置 EIP
print_eip_table(idle_eips, "⚠️ 闲置 EIP 列表", min_idle_days)
# 生成分析报告
generate_report(all_eips, idle_eips, min_idle_days)
# 输出 JSON 格式报告(可选)
if '--json' in sys.argv:
report = {
"region": region_id,
"timestamp": datetime.now().isoformat(),
"summary": {
"total_eips": len(all_eips),
"idle_eips": len(idle_eips),
"idle_rate": len(idle_eips) / len(all_eips) * 100 if all_eips else 0,
"estimated_monthly_cost_cny": len(idle_eips) * 0.02 * 24 * 30
},
"idle_eip_details": [
{
"eip_id": getattr(eip, 'id', 'N/A'),
"public_ip": getattr(eip, 'public_ip_address', 'N/A'),
"bandwidth_id": getattr(eip, 'bandwidth_id', 'N/A'),
"create_time": getattr(eip, 'create_time', 'N/A'),
"idle_days": calculate_idle_days(getattr(eip, 'create_time', ''))
}
for eip in idle_eips
],
"recommendations": [
"确认闲置 EIP 的业务用途,联系负责人核实",
"对确认不用的 EIP,手动在控制台释放",
"对可能需要但暂时闲置的 EIP,可调整为最低带宽(1 Mbps)",
"定期审计,建议每周运行此脚本",
"为 EIP 添加用途标签,便于后续识别和管理"
]
}
print("\n📄 JSON 报告:")
print(json.dumps(report, indent=2, ensure_ascii=False))
if __name__ == '__main__':
main()
#!/bin/bash
#
# eip_audit_log.sh - EIP 操作审计日志
#
# 功能:
# - 记录所有 EIP 操作历史
# - 支持 JSON 格式审计日志
# - 支持审计日志查询和导出
# - 符合等保合规要求
#
# 使用方法:
# ./eip_audit_log.sh --action release --eip-id eip-xxx --operator admin
# ./eip_audit_log.sh --query --days 30
# ./eip_audit_log.sh --export --format csv
#
# 环境变量:
# EIP_AUDIT_LOG_DIR - 审计日志目录(默认:~/.eip-audit-logs)
#
set -e
# 默认配置
AUDIT_LOG_DIR="${EIP_AUDIT_LOG_DIR:-$HOME/.eip-audit-logs}"
AUDIT_LOG_FILE="$AUDIT_LOG_DIR/audit_$(date +%Y%m).jsonl"
# 创建审计日志目录
mkdir -p "$AUDIT_LOG_DIR"
# 解析参数
ACTION=""
EIP_ID=""
OPERATOR="${USER:-admin}"
DETAILS=""
QUERY_MODE=false
QUERY_DAYS=30
EXPORT_MODE=false
EXPORT_FORMAT="json"
while [[ $# -gt 0 ]]; do
case $1 in
--action)
ACTION="$2"
shift 2
;;
--eip-id)
EIP_ID="$2"
shift 2
;;
--operator)
OPERATOR="$2"
shift 2
;;
--details)
DETAILS="$2"
shift 2
;;
--query)
QUERY_MODE=true
shift
;;
--days)
QUERY_DAYS="$2"
shift 2
;;
--export)
EXPORT_MODE=true
shift
;;
--format)
EXPORT_FORMAT="$2"
shift 2
;;
--log-dir)
AUDIT_LOG_DIR="$2"
AUDIT_LOG_FILE="$AUDIT_LOG_DIR/audit_$(date +%Y%m).jsonl"
shift 2
;;
--help)
echo "用法:$0 [选项]"
echo ""
echo "记录操作:"
echo " --action ACTION 操作类型 (release, create, update_bandwidth, bind, unbind)"
echo " --eip-id EIP_ID EIP ID"
echo " --operator OPERATOR 操作人员"
echo " --details DETAILS 操作详情(JSON 格式)"
echo ""
echo "查询模式:"
echo " --query 查询审计日志"
echo " --days DAYS 查询最近 N 天的日志(默认:30)"
echo ""
echo "导出模式:"
echo " --export 导出审计日志"
echo " --format FORMAT 导出格式:json, csv, html(默认:json)"
echo ""
echo "其他选项:"
echo " --log-dir DIR 审计日志目录"
echo " --help 显示帮助信息"
exit 0
;;
*)
echo "未知选项:$1"
exit 1
;;
esac
done
# 查询模式
if [ "$QUERY_MODE" = true ]; then
echo "🔍 查询最近 $QUERY_DAYS 天的审计日志..."
echo ""
if [ ! -f "$AUDIT_LOG_FILE" ] && [ -z "$(ls -A $AUDIT_LOG_DIR/*.jsonl 2>/dev/null)" ]; then
echo "ℹ️ 暂无审计日志记录"
exit 0
fi
# 计算日期阈值
THRESHOLD_DATE=$(date -d "$QUERY_DAYS days ago" +%Y%m%d 2>/dev/null || date -v-${QUERY_DAYS}d +%Y%m%d 2>/dev/null || echo "0")
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf "%-20s %-15s %-20s %-15s\n" "时间" "操作类型" "EIP ID" "操作人员"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 遍历所有日志文件
for LOG_FILE in "$AUDIT_LOG_DIR"/audit_*.jsonl; do
[ -f "$LOG_FILE" ] || continue
# 使用 jq 读取所有 JSON 对象(支持多行格式)
jq -c '.' "$LOG_FILE" 2>/dev/null | while IFS= read -r LINE; do
TIMESTAMP=$(echo "$LINE" | jq -r '.timestamp' 2>/dev/null)
FILE_DATE=$(echo "$TIMESTAMP" | cut -d'T' -f1 | tr -d '-')
# 只显示指定天数内的日志
if [ "$FILE_DATE" -ge "$THRESHOLD_DATE" ] 2>/dev/null; then
printf "%-20s %-15s %-20s %-15s\n" \
"$(echo "$TIMESTAMP" | cut -d'T' -f1) $(echo "$TIMESTAMP" | cut -d'T' -f2 | cut -d'.' -f1)" \
"$(echo "$LINE" | jq -r '.operation' 2>/dev/null)" \
"$(echo "$LINE" | jq -r '.eip_id' 2>/dev/null)" \
"$(echo "$LINE" | jq -r '.operator' 2>/dev/null)"
fi
done
done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
exit 0
fi
# 导出模式
if [ "$EXPORT_MODE" = true ]; then
echo "📊 导出审计日志(格式:$EXPORT_FORMAT)..."
echo ""
EXPORT_FILE="$AUDIT_LOG_DIR/export_$(date +%Y%m%d_%H%M%S).$EXPORT_FORMAT"
case $EXPORT_FORMAT in
json)
echo "[" > "$EXPORT_FILE"
FIRST=true
for LOG_FILE in "$AUDIT_LOG_DIR"/audit_*.jsonl; do
[ -f "$LOG_FILE" ] || continue
while IFS= read -r LINE; do
if [ "$FIRST" = true ]; then
FIRST=false
else
echo "," >> "$EXPORT_FILE"
fi
echo "$LINE" >> "$EXPORT_FILE"
done < "$LOG_FILE"
done
echo "]" >> "$EXPORT_FILE"
;;
csv)
echo "timestamp,operation,eip_id,operator,details" > "$EXPORT_FILE"
for LOG_FILE in "$AUDIT_LOG_DIR"/audit_*.jsonl; do
[ -f "$LOG_FILE" ] || continue
while IFS= read -r LINE; do
TIMESTAMP=$(echo "$LINE" | jq -r '.timestamp' 2>/dev/null)
OPERATION=$(echo "$LINE" | jq -r '.operation' 2>/dev/null)
EIP_ID_VAL=$(echo "$LINE" | jq -r '.eip_id' 2>/dev/null)
OPERATOR_VAL=$(echo "$LINE" | jq -r '.operator' 2>/dev/null)
DETAILS_VAL=$(echo "$LINE" | jq -r '.details' 2>/dev/null | tr ',' ';' | tr '"' "'")
echo "$TIMESTAMP,$OPERATION,$EIP_ID_VAL,$OPERATOR_VAL,\"$DETAILS_VAL\"" >> "$EXPORT_FILE"
done < "$LOG_FILE"
done
;;
html)
cat > "$EXPORT_FILE" << 'EOF'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>EIP 审计日志</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>📋 EIP 操作审计日志</h1>
<p>导出时间:EOF
echo "$(date '+%Y-%m-%d %H:%M:%S')" >> "$EXPORT_FILE"
cat >> "$EXPORT_FILE" << 'EOF'
</p>
<table>
<tr>
<th>时间</th>
<th>操作类型</th>
<th>EIP ID</th>
<th>操作人员</th>
<th>详情</th>
</tr>
EOF
for LOG_FILE in "$AUDIT_LOG_DIR"/audit_*.jsonl; do
[ -f "$LOG_FILE" ] || continue
while IFS= read -r LINE; do
TIMESTAMP=$(echo "$LINE" | jq -r '.timestamp' 2>/dev/null)
OPERATION=$(echo "$LINE" | jq -r '.operation' 2>/dev/null)
EIP_ID_VAL=$(echo "$LINE" | jq -r '.eip_id' 2>/dev/null)
OPERATOR_VAL=$(echo "$LINE" | jq -r '.operator' 2>/dev/null)
DETAILS_VAL=$(echo "$LINE" | jq -r '.details' 2>/dev/null | tr '"' "'")
echo " <tr><td>$TIMESTAMP</td><td>$OPERATION</td><td>$EIP_ID_VAL</td><td>$OPERATOR_VAL</td><td>$DETAILS_VAL</td></tr>" >> "$EXPORT_FILE"
done < "$LOG_FILE"
done
cat >> "$EXPORT_FILE" << 'EOF'
</table>
</body>
</html>
EOF
;;
*)
echo "不支持的导出格式:$EXPORT_FORMAT"
exit 1
;;
esac
echo "✓ 导出成功:$EXPORT_FILE"
exit 0
fi
# 记录审计日志模式
if [ -n "$ACTION" ] && [ -n "$EIP_ID" ]; then
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# 构建审计日志条目
if [ -n "$DETAILS" ]; then
# 如果 details 已经是 JSON 对象(以{开头),直接使用;否则作为字符串处理
if [[ "$DETAILS" == "{"* ]]; then
DETAILS_JSON="$DETAILS"
else
DETAILS_JSON="\"$DETAILS\""
fi
else
DETAILS_JSON="{}"
fi
AUDIT_ENTRY=$(cat << EOF
{
"timestamp": "$TIMESTAMP",
"operation": "$ACTION",
"eip_id": "$EIP_ID",
"operator": "$OPERATOR",
"region": "${HW_REGION:-cn-north-4}",
"details": $DETAILS_JSON
}
EOF
)
# 追加到审计日志文件
echo "$AUDIT_ENTRY" >> "$AUDIT_LOG_FILE"
echo "✓ 审计日志已记录"
echo " 操作:$ACTION"
echo " EIP ID: $EIP_ID"
echo " 操作人员:$OPERATOR"
echo " 时间:$TIMESTAMP"
echo " 日志文件:$AUDIT_LOG_FILE"
exit 0
else
echo "⚠️ 请提供 --action 和 --eip-id 参数,或使用 --query/--export 模式"
echo ""
echo "示例:"
echo " # 记录释放操作"
echo " $0 --action release --eip-id eip-xxx --operator admin"
echo ""
echo " # 查询最近 30 天日志"
echo " $0 --query --days 30"
echo ""
echo " # 导出 CSV 格式日志"
echo " $0 --export --format csv"
exit 1
fi
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
eip_cost_report.py - 华为云 EIP 成本分析报告生成器
基于华为云 Python SDK v2
Usage:
python3 eip_cost_report.py --region cn-north-4
python3 eip_cost_report.py --idle-days 7
python3 eip_cost_report.py --output report.html
"""
import os
import sys
import json
import argparse
from datetime import datetime, timedelta
from typing import List, Dict, Any, Tuple
# 颜色输出
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
CYAN = '\033[96m'
RESET = '\033[0m'
BOLD = '\033[1m'
def color_print(color: str, message: str):
"""彩色输出"""
print(f"{color}{message}{Colors.RESET}")
def get_client():
"""创建 EIP 客户端 (v2 SDK)"""
from huaweicloudsdkeip.v2 import EipClient
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkcore.auth.credentials import BasicCredentials
ak = os.environ.get('HUAWEI_CLOUD_AK')
sk = os.environ.get('HUAWEI_CLOUD_SK')
region = os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')
if not ak or not sk:
color_print(Colors.RED, "❌ Error: Missing credentials")
print("\nPlease set environment variables:")
print(" export HUAWEI_CLOUD_AK=<your-ak>")
print(" export HUAWEI_CLOUD_SK=<your-sk>")
sys.exit(1)
credentials = BasicCredentials(ak=ak, sk=sk)
client = EipClient.new_builder() \
.with_credentials(credentials) \
.with_region(EipRegion.value_of(region)) \
.build()
return client, region
def get_eip_list(client, region: str) -> List[Dict[str, Any]]:
"""获取 EIP 列表"""
try:
from huaweicloudsdkeip.v2 import ListPublicipsRequest
request = ListPublicipsRequest()
request.limit = 100
response = client.list_publicips(request)
eips = response.publicips if hasattr(response, 'publicips') else []
# 转换为字典格式
result = []
for eip in eips:
eip_dict = {
'id': eip.id,
'public_ip_address': eip.public_ip_address,
'status': eip.status,
'bandwidth_size': eip.bandwidth_size if hasattr(eip, 'bandwidth_size') else 0,
'bandwidth_charge_mode': eip.bandwidth_charge_mode if hasattr(eip, 'bandwidth_charge_mode') else 'bandwidth',
'create_time': eip.create_time if hasattr(eip, 'create_time') else None,
'port_id': eip.port_id if hasattr(eip, 'port_id') else None,
'tags': [],
}
result.append(eip_dict)
return result
except Exception as e:
color_print(Colors.RED, f"❌ 获取 EIP 列表失败:{str(e)}")
return []
def get_eip_tags(client, eip_id: str) -> List[Dict[str, str]]:
"""获取 EIP 标签"""
try:
from huaweicloudsdkeip.v2 import ShowPublicipRequest
request = ShowPublicipRequest()
request.publicip_id = eip_id
response = client.show_publicip(request)
eip = response.publicip
if hasattr(eip, 'tags') and eip.tags:
return [{'key': tag.key, 'value': tag.value} for tag in eip.tags]
except Exception:
pass
return []
def has_protected_tag(eip_tags: List[Dict], protected_tags: List[Tuple[str, str]]) -> bool:
"""检查 EIP 是否有保护标签"""
if not protected_tags:
return False
for tag in eip_tags:
tag_key = tag.get('key', '')
tag_value = tag.get('value', '')
for pk, pv in protected_tags:
if tag_key == pk and tag_value == pv:
return True
return False
def is_idle_eip(eip: Dict[str, Any], idle_days: int = 7, protected_tags: List[Tuple[str, str]] = None) -> Tuple[bool, str]:
"""判断 EIP 是否闲置
闲置原因:
- unbound: 未绑定
- protected: 有保护标签(附加标记,不单独判定闲置)
- "": 非闲置
"""
# 检查保护标签
if has_protected_tag(eip.get('tags', []), protected_tags or []):
return False, 'protected'
# 检查是否未绑定
if eip.get('status') == 'DOWN' or not eip.get('port_id'):
# 简单判断:状态为 DOWN 或未绑定资源
return True, 'unbound'
return False, ''
def calculate_cost(bandwidth_size: int, charge_mode: str = 'bandwidth') -> float:
"""计算 EIP 月度成本(简化估算)
华为云 EIP 计费规则(参考):
- 带宽计费:约 2 元/Mbps/月
- 流量计费:按实际使用量
"""
if charge_mode == 'bandwidth':
# 带宽计费:2 元/Mbps/月
return bandwidth_size * 2.0
else:
# 流量计费:估算为带宽计费的 50%
return bandwidth_size * 1.0
def generate_report(eips: List[Dict], region: str, idle_days: int = 7, protected_tags: List[Tuple[str, str]] = None):
"""生成成本分析报告"""
color_print(Colors.BLUE, "=" * 70)
color_print(Colors.BLUE, f" 华为云 EIP 成本分析报告")
color_print(Colors.BLUE, f" 区域:{region} | 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
color_print(Colors.BLUE, "=" * 70)
print()
# 分类统计
idle_eips = []
active_eips = []
protected_eips = []
total_cost = 0
idle_cost = 0
for eip in eips:
# 获取标签
from huaweicloudsdkeip.v2 import EipClient
# 注意:这里需要 client 参数,简化处理不获取标签
cost = calculate_cost(eip.get('bandwidth_size', 0), eip.get('bandwidth_charge_mode'))
eip['monthly_cost'] = cost
total_cost += cost
is_idle, reason = is_idle_eip(eip, idle_days, protected_tags)
if reason == 'protected':
protected_eips.append(eip)
active_eips.append(eip) # 保护的 EIP 算作活跃
elif is_idle:
idle_eips.append(eip)
idle_cost += cost
else:
active_eips.append(eip)
# 输出总览
color_print(Colors.BOLD, "📊 资源总览:")
print(f" 总 EIP 数: {len(eips)}")
print(f" 活跃 EIP: {len(active_eips)}")
print(f" 闲置 EIP: {Colors.RED}{len(idle_eips)}{Colors.RESET}")
if protected_eips:
print(f" 受保护 EIP: {Colors.GREEN}{len(protected_eips)}{Colors.RESET}")
print()
# 成本统计
color_print(Colors.BOLD, "💰 成本统计:")
print(f" 月度总成本: ¥{total_cost:.2f}")
print(f" 闲置浪费: {Colors.RED}¥{idle_cost:.2f}{Colors.RESET}")
if total_cost > 0:
waste_ratio = (idle_cost / total_cost) * 100
print(f" 浪费比例: {Colors.YELLOW}{waste_ratio:.1f}%{Colors.RESET}")
print()
# 闲置 EIP 详情
if idle_eips:
color_print(Colors.RED, "⚠️ 闲置 EIP 列表:")
print()
for i, eip in enumerate(idle_eips, 1):
print(f" [{i}] {Colors.BOLD}{eip['public_ip_address']}{Colors.RESET}")
print(f" EIP ID: {eip['id']}")
print(f" 带宽: {eip['bandwidth_size']} Mbps")
print(f" 月成本: ¥{eip['monthly_cost']:.2f}")
print(f" 状态: {eip['status']}")
if eip.get('create_time'):
print(f" 创建时间: {eip['create_time']}")
print()
color_print(Colors.YELLOW, "💡 优化建议:")
print(" 1. 及时释放不再使用的闲置 EIP")
print(" 2. 对临时使用的 EIP 设置标签标记(如:env=test)")
print(" 3. 定期(每周)运行闲置检测脚本")
print()
else:
color_print(Colors.GREEN, "✅ 未发现闲置 EIP,资源利用率良好!")
print()
# 活跃 EIP 摘要
color_print(Colors.GREEN, "📋 活跃 EIP 摘要:")
print()
for eip in active_eips[:5]: # 只显示前 5 个
print(f" • {eip['public_ip_address']} - {eip['bandwidth_size']} Mbps - ¥{eip['monthly_cost']:.2f}/月")
if len(active_eips) > 5:
print(f" ... 还有 {len(active_eips) - 5} 个活跃 EIP")
print()
color_print(Colors.BLUE, "=" * 70)
def main():
parser = argparse.ArgumentParser(description='华为云 EIP 成本分析报告')
parser.add_argument('--region', type=str, default=None, help='区域(默认:HUAWEI_CLOUD_REGION 或 cn-north-4)')
parser.add_argument('--idle-days', type=int, default=7, help='闲置天数阈值(默认:7)')
parser.add_argument('--protected-tags', type=str, default=None, help='保护标签(格式:key1=value1,key2=value2)')
parser.add_argument('--output', type=str, default=None, help='输出文件路径(HTML/JSON)')
args = parser.parse_args()
client, region = get_client()
if args.region:
region = args.region
# 解析保护标签
protected_tags = []
if args.protected_tags:
for item in args.protected_tags.split(','):
if '=' in item:
key, value = item.split('=', 1)
protected_tags.append((key.strip(), value.strip()))
# 获取 EIP 列表
color_print(Colors.BLUE, "🔍 正在扫描 EIP 资源...")
eips = get_eip_list(client, region)
if not eips:
color_print(Colors.YELLOW, "⚠️ 当前区域没有找到 EIP 资源")
sys.exit(0)
# 生成报告
generate_report(eips, region, args.idle_days, protected_tags)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
列出所有 EIP 及其详细信息
基于华为云 Python SDK v2
Usage:
python3 list_eips.py --region cn-north-4
python3 list_eips.py --status BINDING
python3 list_eips.py --idle-only
"""
import os
import sys
from datetime import datetime
from typing import List, Optional
# 颜色输出
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
CYAN = '\033[96m'
RESET = '\033[0m'
BOLD = '\033[1m'
def color_print(color: str, message: str):
"""彩色输出"""
print(f"{color}{message}{Colors.RESET}")
def get_client():
"""创建 EIP 客户端 (v2 SDK)"""
from huaweicloudsdkeip.v2 import EipClient
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkcore.auth.credentials import BasicCredentials
ak = os.environ.get('HUAWEI_CLOUD_AK')
sk = os.environ.get('HUAWEI_CLOUD_SK')
region = os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')
if not ak or not sk:
color_print(Colors.RED, "❌ Error: Missing credentials")
print("\nPlease set environment variables:")
print(" export HUAWEI_CLOUD_AK=<your-ak>")
print(" export HUAWEI_CLOUD_SK=<your-sk>")
print(" export HUAWEI_CLOUD_REGION=cn-north-4")
sys.exit(1)
credentials = BasicCredentials(ak=ak, sk=sk)
client = EipClient.new_builder() \
.with_credentials(credentials) \
.with_region(EipRegion.value_of(region)) \
.build()
return client, region
def list_all_eips(client):
"""获取所有 EIP 列表(返回原始数据供其他脚本使用)"""
from huaweicloudsdkeip.v2 import ListPublicipsRequest
request = ListPublicipsRequest()
request.limit = 1000
response = client.list_publicips(request)
return response.publicips if hasattr(response, 'publicips') else []
def list_eips(region_filter: Optional[str] = None, status_filter: Optional[str] = None, idle_only: bool = False):
"""列出所有 EIP"""
client, region = get_client()
if region_filter:
region = region_filter
color_print(Colors.BLUE, "=" * 60)
color_print(Colors.BLUE, f" EIP 列表查询(区域:{region})")
color_print(Colors.BLUE, "=" * 60)
print()
try:
eips = list_all_eips(client)
if not eips:
color_print(Colors.YELLOW, "⚠️ 当前区域没有找到 EIP 资源")
return
# 过滤和统计
filtered_eips = []
idle_count = 0
binding_count = 0
total_bandwidth = 0
for eip in eips:
# 状态过滤
if status_filter and eip.status != status_filter:
continue
# 闲置过滤
is_idle = (eip.status == 'DOWN' or eip.status == 'ELB')
if idle_only and not is_idle:
continue
filtered_eips.append(eip)
if is_idle:
idle_count += 1
else:
binding_count += 1
if hasattr(eip, 'bandwidth_size') and eip.bandwidth_size:
total_bandwidth += eip.bandwidth_size
# 输出结果
color_print(Colors.GREEN, f"📊 找到 {len(filtered_eips)} 个 EIP")
print()
for i, eip in enumerate(filtered_eips, 1):
is_idle = (eip.status == 'DOWN' or eip.status == 'ELB')
status_color = Colors.RED if is_idle else Colors.GREEN
status_text = "IDLE" if is_idle else eip.status
print(f"[{i}] {Colors.BOLD}{eip.public_ip_address}{Colors.RESET}")
print(f" EIP ID: {eip.id}")
print(f" 状态: {status_color}{status_text}{Colors.RESET}")
print(f" 带宽大小: {eip.bandwidth_size if hasattr(eip, 'bandwidth_size') else 'N/A'} Mbps")
print(f" 计费模式: {eip.bandwidth_charge_mode if hasattr(eip, 'bandwidth_charge_mode') else 'N/A'}")
if hasattr(eip, 'port_id') and eip.port_id:
print(f" 绑定资源: {eip.port_id}")
else:
print(f" 绑定资源: {Colors.YELLOW}未绑定{Colors.RESET}")
if hasattr(eip, 'create_time') and eip.create_time:
print(f" 创建时间: {eip.create_time}")
print()
# 汇总统计
color_print(Colors.BLUE, "-" * 60)
color_print(Colors.BOLD, "📈 汇总统计:")
print(f" 总 EIP 数: {len(filtered_eips)}")
print(f" 闲置 EIP: {Colors.RED}{idle_count}{Colors.RESET}")
print(f" 使用中 EIP: {Colors.GREEN}{binding_count}{Colors.RESET}")
print(f" 总带宽: {total_bandwidth} Mbps")
color_print(Colors.BLUE, "-" * 60)
except Exception as e:
color_print(Colors.RED, f"❌ 查询失败:{str(e)}")
sys.exit(1)
def main():
import argparse
parser = argparse.ArgumentParser(description='列出所有华为云 EIP')
parser.add_argument('--region', type=str, default=None, help='区域(默认:HUAWEI_CLOUD_REGION 或 cn-north-4)')
parser.add_argument('--status', type=str, default=None, help='状态过滤(BINDING, DOWN, ELB, etc.)')
parser.add_argument('--idle-only', action='store_true', help='仅显示闲置 EIP')
parser.add_argument('--summary', action='store_true', help='仅输出统计数据(CSV 格式:总数,闲置数,总带宽)')
args = parser.parse_args()
# 如果是 summary 模式,输出 CSV 格式
if args.summary:
try:
client, region = get_client()
eips = list_all_eips(client)
total_count = len(eips)
idle_count = sum(1 for e in eips if hasattr(e, 'status') and e.status in ['DOWN', 'ELB'])
total_bandwidth = sum(e.bandwidth_size for e in eips if hasattr(e, 'bandwidth_size') and e.bandwidth_size)
# 输出 CSV 格式:总数,闲置数,总带宽
print(f"{total_count},{idle_count},{total_bandwidth}")
except Exception:
print("0,0,0")
return
list_eips(
region_filter=args.region,
status_filter=args.status,
idle_only=args.idle_only
)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
闲置 EIP 监控与告警工具
基于华为云 Python SDK v2,支持:
- 定时扫描闲置 EIP
- 企业微信/钉钉 webhook 通知
- 邮件告警
- 自定义闲置阈值
Usage:
python3 monitor_idle_eips.py --scan # 扫描并报告闲置 EIP
python3 monitor_idle_eips.py --scan --wechat-webhook URL # 发送微信通知
python3 monitor_idle_eips.py --setup-cron # 设置定时监控(每天 9:00)
"""
import os
import sys
import json
import argparse
from datetime import datetime, timedelta
from typing import List, Dict, Optional
import urllib.request
import urllib.error
import smtplib
from email.mime.text import MIMEText
from email.header import Header
# 颜色输出
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
RESET = '\033[0m'
BOLD = '\033[1m'
def color_print(color: str, message: str):
"""彩色输出"""
print(f"{color}{message}{Colors.RESET}")
def get_client():
"""创建 EIP 客户端 (v2 SDK)"""
from huaweicloudsdkeip.v2 import EipClient
from huaweicloudsdkeip.v2.region.eip_region import EipRegion
from huaweicloudsdkcore.auth.credentials import BasicCredentials
ak = os.environ.get('HUAWEI_CLOUD_AK')
sk = os.environ.get('HUAWEI_CLOUD_SK')
region = os.environ.get('HUAWEI_CLOUD_REGION', 'cn-north-4')
if not ak or not sk:
color_print(Colors.RED, "❌ 错误:未检测到认证信息")
color_print(Colors.YELLOW, "请设置环境变量 HUAWEI_CLOUD_AK 和 HUAWEI_CLOUD_SK")
sys.exit(1)
credentials = BasicCredentials(ak=ak, sk=sk)
client = EipClient.new_builder() \
.with_credentials(credentials) \
.with_region(EipRegion.value_of(region)) \
.build()
return client, region
def list_all_eips(client) -> List[dict]:
"""列出所有 EIP"""
from huaweicloudsdkeip.v2 import ListPublicipsRequest
try:
request = ListPublicipsRequest()
request.limit = 1000
response = client.list_publicips(request)
eips = []
if response.publicips:
for eip in response.publicips:
# 解析创建时间
created_at = getattr(eip, 'create_time', None)
created_datetime = None
if created_at:
try:
# 处理 ISO 格式时间戳
if 'T' in created_at:
created_datetime = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
else:
created_datetime = datetime.strptime(created_at[:19], '%Y-%m-%d %H:%M:%S')
except:
pass
eip_data = {
'id': eip.id,
'public_ip_address': eip.public_ip_address,
'status': eip.status,
'port_id': getattr(eip, 'port_id', None),
'bandwidth_size': getattr(eip, 'bandwidth_size', None),
'bandwidth_id': getattr(eip, 'bandwidth_id', None),
'created_at': created_at,
'created_datetime': created_datetime,
}
eips.append(eip_data)
return eips
except Exception as e:
color_print(Colors.RED, f"查询 EIP 失败:{e}")
return []
def identify_idle_eips(eips: List[dict], idle_days_threshold: int = 7) -> List[dict]:
"""识别闲置 EIP(未绑定且超过阈值天数)"""
idle = []
now = datetime.now()
for eip in eips:
port_id = eip.get('port_id')
created_datetime = eip.get('created_datetime')
# 未绑定 (port_id 为 None 或空)
if not port_id or port_id == '':
# 检查是否超过闲置天数阈值
if created_datetime:
days_since_creation = (now - created_datetime).days
if days_since_creation >= idle_days_threshold:
eip['idle_days'] = days_since_creation
idle.append(eip)
else:
# 无法确定创建时间,默认视为闲置
eip['idle_days'] = -1 # 未知
idle.append(eip)
return idle
def send_wechat_webhook(webhook_url: str, title: str, content: str, idle_eips: List[dict]) -> bool:
"""发送企业微信/钉钉 webhook 通知"""
try:
# 构建Markdown 消息
markdown_content = f"## {title}\n\n"
markdown_content += f"**扫描时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
markdown_content += f"### 📊 发现 {len(idle_eips)} 个闲置 EIP\n\n"
if idle_eips:
markdown_content += "| EIP ID | IP 地址 | 带宽 | 闲置天数 |\n"
markdown_content += "|--------|---------|------|----------|\n"
for eip in idle_eips[:10]: # 最多显示 10 个
eip_id = eip['id'][:8] + '...'
ip = eip['public_ip_address']
bw = f"{eip['bandwidth_size']} Mbps" if eip['bandwidth_size'] else 'N/A'
idle_days = f"{eip['idle_days']} 天" if eip['idle_days'] > 0 else '未知'
markdown_content += f"| {eip_id} | {ip} | {bw} | {idle_days} |\n"
if len(idle_eips) > 10:
markdown_content += f"\n... 还有 {len(idle_eips) - 10} 个闲置 EIP\n"
markdown_content += f"\n{content}"
# 企业微信格式
data = {
"msgtype": "markdown",
"markdown": {
"content": markdown_content
}
}
req = urllib.request.Request(
webhook_url,
data=json.dumps(data).encode('utf-8'),
headers={'Content-Type': 'application/json'}
)
response = urllib.request.urlopen(req, timeout=10)
result = json.loads(response.read().decode('utf-8'))
# 企业微信返回 errcode 0 表示成功
if result.get('errcode', 0) == 0 or result.get('code', 0) == 0:
color_print(Colors.GREEN, "✅ 微信通知发送成功")
return True
else:
color_print(Colors.YELLOW, f"⚠️ 微信通知返回异常:{result}")
return False
except Exception as e:
color_print(Colors.RED, f"❌ 发送微信通知失败:{e}")
return False
def send_email_alert(email_to: str, email_smtp: str, email_port: int, email_user: str, email_pass: str,
title: str, content: str, idle_eips: List[dict]) -> bool:
"""发送邮件告警"""
try:
# 构建邮件内容
html_content = f"""
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #4CAF50; color: white; }}
tr:nth-child(even) {{ background-color: #f2f2f2; }}
.warning {{ color: #ff9800; font-weight: bold; }}
</style>
</head>
<body>
<h2>📊 闲置 EIP 监控告警</h2>
<p><strong>扫描时间</strong>: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p class="warning">发现 {len(idle_eips)} 个闲置 EIP</p>
<table>
<tr>
<th>EIP ID</th>
<th>IP 地址</th>
<th>带宽</th>
<th>状态</th>
<th>闲置天数</th>
</tr>
"""
for eip in idle_eips[:20]: # 最多显示 20 个
eip_id = eip['id'][:8] + '...'
ip = eip['public_ip_address']
bw = f"{eip['bandwidth_size']} Mbps" if eip['bandwidth_size'] else 'N/A'
idle_days = f"{eip['idle_days']} 天" if eip['idle_days'] > 0 else '未知'
html_content += f"""
<tr>
<td>{eip_id}</td>
<td>{ip}</td>
<td>{bw}</td>
<td>{eip['status']}</td>
<td class="warning">{idle_days}</td>
</tr>
"""
html_content += f"""
</table>
<p><strong>建议操作</strong>:</p>
<ul>
<li>确认这些 EIP 是否确实不再使用</li>
<li>如确认闲置,请及时释放以节省成本</li>
<li>如需保留,请绑定到相应资源</li>
</ul>
<hr>
<p style="color: #666; font-size: 12px;">此邮件由华为云 EIP 监控工具自动生成</p>
</body>
</html>
"""
msg = MIMEText(html_content, 'html', 'utf-8')
msg['Subject'] = Header(title, 'utf-8')
msg['From'] = email_user
msg['To'] = email_to
# 发送邮件
server = smtplib.SMTP_SSL(email_smtp, email_port)
server.login(email_user, email_pass)
server.sendmail(email_user, [email_to], msg.as_string())
server.quit()
color_print(Colors.GREEN, "✅ 邮件告警发送成功")
return True
except Exception as e:
color_print(Colors.RED, f"❌ 发送邮件告警失败:{e}")
return False
def print_idle_eips_report(idle_eips: List[dict], region: str):
"""打印闲置 EIP 报告"""
if not idle_eips:
color_print(Colors.GREEN, "\n✅ 未发现闲置 EIP,所有 EIP 都在使用中")
return
print("\n" + "=" * 100)
print(f"⚠️ 闲置 EIP 监控报告 (区域:{region})")
print("=" * 100)
print(f"扫描时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"发现闲置 EIP 数量:{len(idle_eips)} 个")
print("=" * 100)
print(f"{'EIP ID':<40} {'IP 地址':<16} {'带宽':<10} {'状态':<10} {'闲置天数':<10} {'创建时间'}")
print("-" * 100)
for eip in idle_eips:
eip_id = eip.get('id', 'N/A')[:38]
ip = eip.get('public_ip_address', 'N/A')
bw_size = eip.get('bandwidth_size', 'N/A')
status = eip.get('status', 'UNKNOWN')
idle_days = f"{eip.get('idle_days', 'N/A')} 天" if eip.get('idle_days', -1) > 0 else "未知"
created_at = eip.get('created_at', 'N/A')
if created_at and created_at != 'N/A':
created_at = created_at.replace('T', ' ').split('.')[0][:19]
bw_display = f"{bw_size} Mbps" if bw_size else "N/A"
print(f"{eip_id:<40} {ip:<16} {bw_display:<10} {status:<10} {idle_days:<10} {created_at}")
print("-" * 100)
# 估算成本浪费
total_bandwidth = sum(eip.get('bandwidth_size', 0) or 0 for eip in idle_eips)
color_print(Colors.YELLOW, f"\n💰 估算带宽资源浪费:{total_bandwidth} Mbps")
color_print(Colors.RED, f"⚠️ 建议及时释放或绑定这些闲置 EIP 以节省成本")
print("=" * 100)
def setup_cron_job(webhook_url: Optional[str] = None, email: Optional[str] = None):
"""设置定时监控任务"""
color_print(Colors.BLUE, "\n📅 设置定时监控任务...")
# 生成 cron 命令
script_path = os.path.abspath(__file__)
cron_command = f"0 9 * * * cd {os.path.dirname(script_path)} && python3 {script_path} --scan"
if webhook_url:
cron_command += f" --wechat-webhook '{webhook_url}'"
if email:
cron_command += f" --email '{email}'"
color_print(Colors.YELLOW, "\n📋 请手动添加以下 cron 任务:")
print(f"\n{cron_command}\n")
color_print(Colors.BLUE, "或者运行以下命令自动添加:")
print(f"\n(crontab -l 2>/dev/null; echo '{cron_command}') | crontab -\n")
# 尝试自动添加
try:
import subprocess
result = subprocess.run(
f"(crontab -l 2>/dev/null; echo '{cron_command}') | crontab -",
shell=True,
capture_output=True,
text=True
)
if result.returncode == 0:
color_print(Colors.GREEN, "✅ 定时任务已成功添加")
color_print(Colors.BLUE, "📅 监控时间:每天早上 9:00 自动扫描")
else:
color_print(Colors.YELLOW, f"⚠️ 自动添加失败:{result.stderr}")
color_print(Colors.BLUE, "请手动执行上面的 crontab 命令")
except Exception as e:
color_print(Colors.YELLOW, f"⚠️ 自动添加失败:{e}")
color_print(Colors.BLUE, "请手动执行 crontab -e 添加上述任务")
def main():
parser = argparse.ArgumentParser(description='闲置 EIP 监控与告警工具')
parser.add_argument('--scan', action='store_true', help='扫描闲置 EIP')
parser.add_argument('--idle-days', type=int, default=7, help='闲置天数阈值(默认:7 天)')
parser.add_argument('--wechat-webhook', type=str, help='企业微信/钉钉 webhook URL')
parser.add_argument('--email', type=str, help='告警邮箱地址')
parser.add_argument('--email-smtp', type=str, default='smtp.163.com', help='SMTP 服务器(默认:smtp.163.com)')
parser.add_argument('--email-port', type=int, default=465, help='SMTP 端口(默认:465)')
parser.add_argument('--email-user', type=str, help='SMTP 登录用户名')
parser.add_argument('--email-pass', type=str, help='SMTP 登录密码/授权码')
parser.add_argument('--setup-cron', action='store_true', help='设置定时监控任务')
args = parser.parse_args()
# 设置定时任务模式
if args.setup_cron:
setup_cron_job(args.wechat_webhook, args.email)
return
# 扫描模式
if not args.scan:
parser.print_help()
print("\n错误:请指定操作模式 (--scan / --setup-cron)")
sys.exit(1)
# 创建客户端
client, region = get_client()
color_print(Colors.BLUE, f"🔍 正在扫描区域 {region} 的闲置 EIP...")
# 查询所有 EIP
all_eips = list_all_eips(client)
if not all_eips:
color_print(Colors.YELLOW, "ℹ️ 未找到任何 EIP")
return
# 识别闲置 EIP
idle_eips = identify_idle_eips(all_eips, args.idle_days)
# 打印报告
print_idle_eips_report(idle_eips, region)
# 发送告警通知
if idle_eips:
title = f"⚠️ 闲置 EIP 告警 - 发现 {len(idle_eips)} 个闲置 EIP"
content = "请及时处理闲置 EIP 以节省成本。"
# 微信通知
if args.wechat_webhook:
send_wechat_webhook(args.wechat_webhook, title, content, idle_eips)
# 邮件通知
if args.email:
if not args.email_user or not args.email_pass:
color_print(Colors.RED, "❌ 发送邮件需要 --email-user 和 --email-pass 参数")
else:
send_email_alert(
args.email,
args.email_smtp,
args.email_port,
args.email_user,
args.email_pass,
title,
content,
idle_eips
)
else:
color_print(Colors.GREEN, "\n✅ 所有 EIP 使用正常,无需告警")
if __name__ == '__main__':
main()
# EIP Configuration Template
# Copy this file and customize for your environment
#
# IMPORTANT: Authentication credentials (AK/SK) must be provided via environment variables:
# export HUAWEI_CLOUD_AK=<your-ak>
# export HUAWEI_CLOUD_SK=<your-sk>
# export HUAWEI_CLOUD_REGION=cn-north-4
#
# NEVER commit real AK/SK values to version control
# Region Configuration
region: cn-north-4
# Idle EIP Detection
idle_detection:
enabled: true
idle_days_threshold: 7
low_traffic_threshold: 5
low_traffic_period_days: 7
protected_tags:
- env:prod
- persistent:true
- protected:true
# Auto Release Policy
auto_release:
enabled: false
require_confirmation: true
exclude_eip_ids: []
# EIPs matching these tags will NOT be auto-released
protected_tags:
- env:prod
- persistent:true
# Bandwidth Policy
bandwidth_policy:
idle_default_bandwidth: 1
max_bandwidth: 200
# Monitoring & Alerting
monitoring:
enabled: true
scan_interval_hours: 24
wechat_webhook: ""
dingtalk_webhook: ""
alert_email: ""
# Cost Report
cost_report:
enabled: true
output_dir: /tmp/eip-reports
price_per_mbps: 2.0
currency: CNY
# Audit Log
audit:
enabled: true
log_dir: ~/.eip-audit-logs
export_format: json