阿里云安全组自动同步动态公网IP脚本

发布时间:2026/9/14 10:07:16
阿里云安全组自动同步动态公网IP脚本 简介这是一套面向阿里云开发者及公有云运维人员的自动化安全组管理工具专为解决家庭宽带或动态拨号环境下本地公网IP频繁变更导致需手动更新阿里云安全组规则的痛点。工具通过Java实现自动获取当前本地公网IP并按预设策略实时同步至指定安全组的入方向规则中显著提升开发测试与远程调试效率。资源包共9个文件含6个核心Java源码涵盖IP获取、API调用、规则比对与更新逻辑、1个可配置properties文件、1个Maven构建pom.xml及1个依赖jar包整体仅294KB轻量易部署。目前已有572人学习下载提供完整可运行工程结构与清晰注释支持快速适配不同安全组规则及迁移至腾讯云、华为云等其他主流云平台。1. 为什么你每次改完服务器IP就得手动点十次安全组这个脚本让它自己动起来你刚在阿里云上部署了一台用于爬虫调度或临时测试的ECS它用的是按量付费弹性公网IPEIPIP每次重启都变或者你用的是NAT网关后挂的私有网络实例靠SNAT出口公网出口IP由阿里云动态分配——这时候你会发现安全组里那条“仅允许我的办公IP访问22端口”的规则三天就失效一次。不是忘了更新是根本来不及——等你发现SSH连不上先查IP、再进控制台、再找安全组、再编辑入方向规则、再保存整个过程平均耗时4分37秒。而真实场景中更多人直接把安全组放行0.0.0.0/0凑合用等于把门锁换成纱窗。本文讲的不是“怎么配安全组”而是让本地机器自动感知自身公网IP变化并毫秒级同步到指定阿里云安全组规则中。它不依赖ECS实例内网元数据因为你的出口可能根本不是这台ECS不走Webhook或第三方服务只用阿里云官方SDK 本地定时探测 原子化更新逻辑。适合运维、SRE、自动化测试工程师以及所有被“动态出口IP严格安全组”组合拳打懵的中小团队。2. 用aliyun-python-sdk-ecs在本地跑通安全组规则原子更新的最小命令2.1 为什么选Python SDK而不是CLI或OpenAPI直调阿里云OpenAPI虽开放但安全组规则更新AuthorizeSecurityGroup/RevokeSecurityGroup存在两个硬约束一是单次调用只能增或删不能同时操作二是规则变更非幂等——重复添加已存在的规则会报错重复删除不存在的规则也报错。这意味着你无法用一条aliyun ecs AuthorizeSecurityGroup ...命令“覆盖写入”必须先查旧IP、再删旧规则、再加新规则。CLI工具如aliyuncli封装层太薄缺乏状态比对和事务兜底而Python SDKaliyun-python-sdk-ecs提供了完整的DescribeSecurityGroupAttribute和RevokeSecurityGroup/AuthorizeSecurityGroup三连调能力且支持异常捕获与重试策略。更重要的是它能天然集成到本地探测逻辑中——你不需要额外起HTTP服务也不用维护Token有效期AccessKey Secret可加密存本地SDK自动处理签名。常见误用是直接调ModifySecurityGroupAttribute但它只改安全组名称/描述不碰规则属于典型选型踩坑。2.2 安装SDK并配置最小权限AccessKeypip install aliyun-python-sdk-ecs aliyun-python-sdk-vpc提示不要用root用户全局安装。建议创建虚拟环境python -m venv ./sg-updater-env source ./sg-updater-env/bin/activate。SDK版本需≥4.22.02023年Q4后发布低版本不支持DescribeSecurityGroupAttribute返回完整规则列表。AccessKey必须授予最小必要权限。在RAM控制台新建自定义策略JSON格式{ Version: 1, Statement: [ { Action: [ ecs:DescribeSecurityGroupAttribute, ecs:AuthorizeSecurityGroup, ecs:RevokeSecurityGroup ], Resource: *, Effect: Allow } ] }绑定该策略到专用子用户严禁使用主账号AK。将AK信息存为~/.aliyun/config.json此路径被SDK默认读取{ access_key_id: LTAI5tQZzXxXxXxXxXxXxXxXxXxXxX, access_key_secret: ZzZzZzZzZzZzZzZzZzZzZzZzZzZzZz, region_id: cn-hangzhou, output_format: json }注意region_id必须填你目标安全组所在的地域如cn-beijing不是你本地机器所在地域。若跨地域操作需在代码中显式指定client AcsClient(..., region_idcn-beijing)否则默认用配置文件里的值导致InvalidRegionId.NotFound错误。2.3 获取本地当前公网IP的三种可靠方式及 fallback 机制不能依赖curl ifconfig.me这类公共接口——它们无SLA、常被墙、返回格式不稳定。生产级方案必须多源探测超时熔断import requests import time def get_public_ip(): # 主源阿里云官方元数据仅限ECS内网调用此处不适用跳过 # 备源1Cloudflare DNS over HTTPS稳定、无广告、返回纯IP try: resp requests.get(https://cloudflare-dns.com/dns-query?ctapplication/dns-jsondofalsenamemyip.opendns.comA, timeout5, headers{Accept: application/dns-json}) if resp.status_code 200: data resp.json() if Answer in data and len(data[Answer]) 0: return data[Answer][0][data].strip() except Exception as e: pass # 备源2Google DNS API备用链路 try: resp requests.get(https://dns.google/resolve?namemyip.opendns.comtypeA, timeout5) if resp.status_code 200: data resp.json() if Answer in data and len(data[Answer]) 0: return data[Answer][0][data].strip() except Exception as e: pass # 终极fallback用系统ifconfig提取仅当明确知道出口网卡名时如enp0s3 try: import subprocess result subprocess.run([ip, route, get, 1], capture_outputTrue, textTrue) if result.returncode 0 and src in result.stdout: return result.stdout.split(src)[1].split()[0].strip() except Exception as e: pass raise RuntimeError(Failed to detect public IP from all sources)逻辑说明优先走DNS-over-HTTPSDoH避免HTTP中间件干扰Cloudflare和Google双源互备最后fallback到系统路由表ip route get 1返回的src地址即本机出口IP。参数说明每个请求设5秒超时避免卡死headers{Accept: application/dns-json}确保Cloudflare返回结构化JSON而非HTMLsubprocess方案仅作保底因Docker容器或NetworkManager环境下可能不准。3. 安全组规则原子更新查-删-增三步不可拆解的实现细节3.1 解析安全组现有规则并精准定位待更新条目关键不是“找到所有22端口规则”而是识别出由本脚本管理的那一条。否则会误删他人添加的规则。约定所有本脚本管理的规则其Description字段必须包含唯一标识符如[AUTO-UPDATE-SSH]。这样即使多人共用一个安全组也能隔离操作范围。from aliyunsdkcore.client import AcsClient from aliyunsdkecs.request.v20140526 import DescribeSecurityGroupAttributeRequest def get_managed_rule_id(client, security_group_id, port, prototcp): request DescribeSecurityGroupAttributeRequest.DescribeSecurityGroupAttributeRequest() request.set_SecurityGroupId(security_group_id) response client.do_action_with_exception(request) data json.loads(response) for permission in data.get(Permissions, {}).get(Permission, []): # 阿里云API返回的Permission可能是list或dict需兼容 if isinstance(permission, dict) and \ permission.get(IpProtocol) proto and \ permission.get(PortRange) f{port}/{port} and \ [AUTO-UPDATE-SSH] in permission.get(Description, ): return permission.get(PermissionId) return None参数说明PortRange格式为22/22非22必须严格匹配Description字段在控制台UI中显示为“描述”SDK中为DescriptionPermissionId是阿里云内部规则ID删除时必需新增时不需提供。3.2 删除旧规则与添加新规则的原子性保障阿里云不提供事务必须用try-except保证“删失败则不增增失败则不删”。但更关键的是避免窗口期暴露如果先删后增中间几秒所有流量被拒绝。解决方案是先增后删利用安全组规则的“白名单叠加”特性——新规则生效后再删旧规则期间始终有至少一条有效规则。from aliyunsdkecs.request.v20140526 import AuthorizeSecurityGroupRequest, RevokeSecurityGroupRequest def update_security_group_rule(client, security_group_id, new_ip, port22): old_rule_id get_managed_rule_id(client, security_group_id, port) # Step 1: 添加新规则带唯一Description标记 auth_req AuthorizeSecurityGroupRequest.AuthorizeSecurityGroupRequest() auth_req.set_SecurityGroupId(security_group_id) auth_req.set_IpPermissions(json.dumps([{ IpProtocol: tcp, PortRange: f{port}/{port}, SourceCidrIp: f{new_ip}/32, Description: [AUTO-UPDATE-SSH] Managed by local updater }])) try: client.do_action_with_exception(auth_req) print(f[INFO] Added new rule for {new_ip}/32) except Exception as e: if InvalidPermission.Duplicate in str(e): print([WARN] New rule already exists, skip adding) else: raise e # Step 2: 删除旧规则仅当存在且不等于新IP时 if old_rule_id and not new_ip.endswith(/32): # 确保new_ip是纯IP old_ip_in_rule None # 从Describe结果中解析old_rule的SourceCidrIp需再次查询因get_managed_rule_id不返回完整字段 desc_req DescribeSecurityGroupAttributeRequest.DescribeSecurityGroupAttributeRequest() desc_req.set_SecurityGroupId(security_group_id) desc_resp client.do_action_with_exception(desc_req) desc_data json.loads(desc_resp) for perm in desc_data.get(Permissions, {}).get(Permission, []): if perm.get(PermissionId) old_rule_id: old_ip_in_rule perm.get(SourceCidrIp, ).split(/)[0] break if old_ip_in_rule and old_ip_in_rule ! new_ip: revoke_req RevokeSecurityGroupRequest.RevokeSecurityGroupRequest() revoke_req.set_SecurityGroupId(security_group_id) revoke_req.set_IpPermissions(json.dumps([{ IpProtocol: tcp, PortRange: f{port}/{port}, SourceCidrIp: f{old_ip_in_rule}/32 }])) try: client.do_action_with_exception(revoke_req) print(f[INFO] Revoked old rule for {old_ip_in_rule}/32) except Exception as e: print(f[ERROR] Failed to revoke old rule: {e})逻辑说明先尝试添加新规则若报InvalidPermission.Duplicate说明已存在比如上次执行中断则跳过再检查旧规则IP是否与新IP不同不同才删——避免无谓的删除操作引发日志噪音。SourceCidrIp必须带/32后缀否则阿里云认为是网段而非单IP。3.3 完整可运行脚本含状态缓存与防抖机制单纯定时任务会导致高频更新如IP未变却每分钟都查。加入本地状态文件记录上次成功更新的IP和时间戳仅当IP变化或超时如24小时未更新才触发同步import json import os from datetime import datetime, timedelta STATE_FILE /var/run/aliyun-sg-updater-state.json def load_state(): if os.path.exists(STATE_FILE): try: with open(STATE_FILE, r) as f: return json.load(f) except Exception: pass return {last_ip: , last_update: 1970-01-01T00:00:00} def save_state(ip): with open(STATE_FILE, w) as f: json.dump({ last_ip: ip, last_update: datetime.now().isoformat() }, f) def main(): client AcsClient( os.getenv(ALIYUN_ACCESS_KEY_ID, your-key), os.getenv(ALIYUN_ACCESS_KEY_SECRET, your-secret), cn-hangzhou # 此处必须与安全组地域一致 ) current_ip get_public_ip() state load_state() # 防抖IP未变且距上次更新不足1小时跳过 last_dt datetime.fromisoformat(state[last_update]) if current_ip state[last_ip] and datetime.now() - last_dt timedelta(hours1): print(f[SKIP] IP unchanged ({current_ip}), last updated {state[last_update]}) return try: update_security_group_rule( clientclient, security_group_idsg-bp1a1b2c3d4e5f6g7h8i, # 替换为你的安全组ID new_ipcurrent_ip, port22 ) save_state(current_ip) print(f[SUCCESS] Security group updated to {current_ip}) except Exception as e: print(f[FATAL] Update failed: {e}) if __name__ __main__: main()提示STATE_FILE路径建议用/var/run/tmpfs内存文件系统避免磁盘IO若无root权限可改用~/.aliyun-sg-state.json。timedelta(hours1)是防抖阈值可根据业务调整——爬虫调度可设为5分钟管理终端可设为2小时。4. 在Linux系统中用systemd timer实现每5分钟自动检测与更新4.1 创建systemd service单元文件创建/etc/systemd/system/aliyun-sg-updater.service[Unit] DescriptionAliyun Security Group Auto Updater Afternetwork.target [Service] Typeoneshot Userdeploy WorkingDirectory/opt/aliyun-sg-updater ExecStart/opt/aliyun-sg-updater/sg-updater-env/bin/python /opt/aliyun-sg-updater/updater.py EnvironmentALIYUN_ACCESS_KEY_IDLTAI5tQZzXxXxXxXxXxXxXxXxXxXxX EnvironmentALIYUN_ACCESS_KEY_SECRETZzZzZzZzZzZzZzZzZzZzZzZzZzZzZz # 不要明文写AK生产环境应使用systemd的EnvironmentFile或密钥管理服务 StandardOutputjournal StandardErrorjournal Restarton-failure RestartSec30 [Install] WantedBymulti-user.target注意Userdeploy指定非root用户运行符合最小权限原则WorkingDirectory必须指向脚本所在目录Environment变量在此处仅为演示生产环境严禁明文存储AK应改用EnvironmentFile/etc/sysconfig/aliyun-sg-updater并在该文件中设置ALIYUN_ACCESS_KEY_ID等变量文件权限600。4.2 创建systemd timer单元文件实现周期触发创建/etc/systemd/system/aliyun-sg-updater.timer[Unit] DescriptionRun Aliyun SG Updater every 5 minutes Requiresaliyun-sg-updater.service [Timer] OnBootSec1min OnUnitActiveSec5min Persistenttrue [Install] WantedBytimers.target参数说明OnBootSec1min表示系统启动后1分钟首次运行避免开机时网络未就绪OnUnitActiveSec5min即每5分钟触发一次Persistenttrue确保宿主机重启后若上次应触发而未触发如关机期间会在开机后立即补触发一次防止规则长期失效。启用并启动timersudo systemctl daemon-reload sudo systemctl enable aliyun-sg-updater.timer sudo systemctl start aliyun-sg-updater.timer sudo systemctl list-timers | grep aliyun验证日志sudo journalctl -u aliyun-sg-updater.service -f # 正常输出示例 # [INFO] Added new rule for 203.208.60.1/32 # [INFO] Revoked old rule for 203.208.60.2/32 # [SUCCESS] Security group updated to 203.208.60.14.3 验证更新效果与失败回滚路径最直接的验证方式是主动触发一次IP变化在本地机器上执行sudo ip addr flush dev eth0 sudo dhclient eth0Linux或断开重连WiFiMac/Windows然后观察日志是否出现[SUCCESS]。但更关键的是验证失败场景失败类型表现应对措施AK权限不足日志报Forbidden.RAM检查RAM策略是否遗漏RevokeSecurityGroup动作安全组ID错误报InvalidSecurityGroupId.NotFound进入ECS控制台复制安全组ID以sg-开头的16位字符串IP探测失败报Failed to detect public IP手动执行curl -s https://cloudflare-dns.com/dns-query?ctapplication/dns-jsondofalsenamemyip.opendns.comA | jq -r .Answer[0].data看是否返回IP规则冲突报InvalidPermission.Duplicate检查是否已有相同Description的规则存在或PortRange格式错误提示所有操作均不修改安全组其他规则只影响带[AUTO-UPDATE-SSH]标记的条目。若需紧急回滚可在控制台手动删除该描述的规则脚本下次运行会重建。5. 进阶技巧支持多端口、多安全组及出口IP白名单批量管理5.1 用YAML配置文件统一管理多目标规则将硬编码的security_group_id、port、proto抽离为配置文件config.yaml支持一配多管regions: - region_id: cn-hangzhou security_groups: - id: sg-bp1a1b2c3d4e5f6g7h8i rules: - port: 22 protocol: tcp description_tag: [AUTO-UPDATE-SSH] - port: 8080 protocol: tcp description_tag: [AUTO-UPDATE-WEBHOOK] - id: sg-bp1j1k2l3m4n5o6p7q8r rules: - port: 3306 protocol: tcp description_tag: [AUTO-UPDATE-DB]解析配置的Python函数import yaml def load_config(config_path/opt/aliyun-sg-updater/config.yaml): with open(config_path, r) as f: return yaml.safe_load(f) def update_all_rules(): config load_config() for region_cfg in config[regions]: client AcsClient(AK, SK, region_cfg[region_id]) for sg_cfg in region_cfg[security_groups]: for rule in sg_cfg[rules]: current_ip get_public_ip() update_security_group_rule( clientclient, security_group_idsg_cfg[id], new_ipcurrent_ip, portrule[port], protorule[protocol], description_tagrule[description_tag] )5.2 实现出口IP白名单的“灰度发布”机制避免一次性全量更新导致误操作。新增--dry-run参数打印将执行的操作而不真实调用APIimport argparse parser argparse.ArgumentParser() parser.add_argument(--dry-run, actionstore_true, helpPrint actions without executing) args parser.parse_args() if args.dry_run: print(f[DRY-RUN] Would add rule for {current_ip}/32 to {sg_id}) print(f[DRY-RUN] Would revoke rule for {old_ip}/32 from {sg_id}) else: # 执行真实更新 update_security_group_rule(...)运行方式python updater.py --dry-run输出清晰的操作预览确认无误后再去掉参数执行。5.3 监控与告警当连续3次更新失败时发送企业微信通知在main()函数末尾添加失败计数器写入/tmp/sg-updater-failures文件FAIL_LOG /tmp/sg-updater-failures def record_failure(): now datetime.now().isoformat() with open(FAIL_LOG, a) as f: f.write(f{now}\n) # 只保留最近10次失败记录 lines open(FAIL_LOG).readlines()[-10:] with open(FAIL_LOG, w) as f: f.writelines(lines) def check_and_alert(): if not os.path.exists(FAIL_LOG): return lines open(FAIL_LOG).readlines() if len(lines) 3: last_3 [line.strip() for line in lines[-3:]] # 调用企业微信机器人需提前配置webhook URL requests.post( https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_WEBHOOK_KEY, json{ msgtype: text, text: { content: f⚠️ Aliyun SG Updater 连续3次失败:\n{chr(10).join(last_3)} } } ) # 清空日志避免重复告警 open(FAIL_LOG, w).close()将check_and_alert()加入main()末尾即可实现故障自检。此机制不依赖外部监控系统轻量且可靠。最后一行技术内容当你的办公网络出口IP由运营商动态分配时该方案能确保安全组规则始终精确收敛到当前有效IP无需人工干预且所有操作留痕可审计——这才是动态IP时代基础设施自动化的正确打开方式。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询