Vultr的Reserved IP要$3/month,Additional IPv4 IP要$2/month,实在是消费不起,手动更换烦了,遂写了个自动程序,来自动更换主机。
想法是这样的,利用定时任务,每隔5分钟检查一次tcp连接状态,3次异常之后,开始进行切换操作。
切换流程
- 获取主机列表
- 停用故障主机
- 创建故障主机快照
- 创建新主机
- 用故障主机快照恢复新主机
- 删除故障主机
测试通过自动化操作,15分钟内自动确认TCP异常,1小时之内就可以自动切换到新主机。
技术细节
在切换过程中有部分操作需要等待执行完成,比如创建快照和从快照恢复,所以使用了swich来进行步骤计数并写入json文件,初始json计数为0,每操作成功一步,增加一位数。好处是当后一步发现之前的结果出错时可以很灵活的进行回滚。
Vultr的API有一个坑,虽然在创建主机的时候提供了snapshot的选项,但是并不会使用这个snapshot创建主机,导致创建出的主机都是毛坯的,还以为是快照出错了。所以还得分成两步,先创建一个空白主机,再从快照恢复,不过恢复后会自动启动主机,这样就少了一个启动的步骤也算是人性化。
在CLI执行ping命令的时候由于会一直ping下去,程序会进入死循环,在命令前加上timeout就可以限制命令执行时间,不过很可惜,在php中执行命令并没有用。
公用函数
注意替换高亮行的API-Key、缓存路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
function callAPI($method, $url, $data){ $curl = curl_init(); switch ($method){ case "POST": curl_setopt($curl, CURLOPT_POST, 1); if ($data) curl_setopt($curl, CURLOPT_POSTFIELDS, $data); break; case "PUT": curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT"); if ($data) curl_setopt($curl, CURLOPT_POSTFIELDS, $data); break; default: if ($data) $url = sprintf("%s?%s", $url, http_build_query($data)); } // OPTIONS: curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'API-Key: your_vultr_api_key', )); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); // EXECUTE: $result = curl_exec($curl); if(!$result){ $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); return $httpcode; } curl_close($curl); return $result; } if ( ! function_exists( 'array_key_last' ) ) { function array_key_last( $array ) { $key = NULL; if ( is_array( $array ) ) { end( $array ); $key = key( $array ); } return $key; } } if (!function_exists('array_key_first')) { function array_key_first(array $array) { if (count($array)) { reset($array); return key($array); } return null; } } function output_json($step){ $outputArray['step'] = $step; $outputJSON = json_encode($outputArray); $fp = fopen('data_auto_swap_ip_vultr.json', 'w') or die("Unable to open file!"); fwrite($fp, $outputJSON); fclose($fp); } function load_json(){ if(file_exists("data_auto_swap_ip_vultr.json")){ $load_json = file_get_contents("data_auto_swap_ip_vultr.json"); }else{ $load_json = '{"step":0}'; echo 'Can not Load last process status, will reset to: '.$load_json; echo "\n"; } return json_decode($load_json, true); } |
步骤零:获取主机列表及程序执行状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
echo 'Get Last Process Status...'; echo "\n"; $last_status = load_json(); echo 'Last process status: '.$last_status['step']; echo "\n"; $get_data = callAPI('GET', 'https://api.vultr.com/v1/server/list', false); $response = json_decode($get_data, true); $server_count = count($response); echo 'Server Number: '.$server_count; echo "\n"; echo 'Get Server Info...'; echo "\n"; reset($response); $first_server_id = array_key_first($response); $first_server_info = $response[array_key_first($response)]; $last_server_id = array_key_last($response); $last_server_info = $response[array_key_last($response)]; echo 'First Server ID: '.$first_server_id; echo "\n"; echo 'First Server Status: '.$first_server_info['power_status']; echo "\n"; echo 'First Server Charges: '.$first_server_info['pending_charges']; echo "\n"; echo 'Last Server ID: '.$last_server_id; echo "\n"; echo 'Last Server Status: '.$last_server_info['power_status']; echo "\n"; echo 'Last Server Charges: '.$last_server_info['pending_charges']; echo "\n"; |
使用状态码执行命令
1 2 3 |
switch ($last_status['step']) { // insert code below } |
步骤一:停用故障主机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
case 0: echo 'Stop old server...'; echo "\n"; if($first_server_info['power_status'] == "running"){ echo 'Server(ID='.$first_server_id.') is running, will stop it'; echo "\n"; echo 'Stop Server(ID='.$first_server_id.')...'; echo "\n"; $data = "SUBID=".$first_server_id; $get_data = callAPI('POST', 'https://api.vultr.com/v1/server/halt', $data); if($get_data = 200){ echo 'Stop Server(ID='.$first_server_id.') Succeed'; echo "\n"; } die(); }else{ echo 'Old Server(ID='.$first_server_id.') is stopped, will continue...'; echo "\n"; } output_json($last_status['step']+1); break; |
步骤二:创建快照
注意高亮行,设置快照有效时间,另时区为UTC+8,创建快照需要等待,此步骤会进行状态判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
case 1: echo 'Create snapshot...'; echo "\n"; // get snapshot list $get_data = callAPI('GET', 'https://api.vultr.com/v1/snapshot/list', false); $response = json_decode($get_data, true); $first_snapshot_id = array_key_first($response); $first_snapshot_info = $response[array_key_first($response)]; echo 'snapshot ID: '.$first_snapshot_id; echo "\n"; $snapshot_status = $first_snapshot_info['status']; echo 'snapshot Status: '.$snapshot_status; echo "\n"; echo 'snapshot create date: '.date("Y-m-d H:i:s",strtotime($first_snapshot_info['date_created'])+8*60*60); echo "\n"; $snapshot_time_age = ((time()-(strtotime($first_snapshot_info['date_created'])+8*60*60))/60); echo 'snapshot time ago: '.$snapshot_time_age.' min'; echo "\n"; $snapshot_time_age_limit = 60; // snapshot time limit if($snapshot_status == 'complete' and $snapshot_time_age <= $snapshot_time_age_limit){ echo 'snapshot has complete and newest (less than '.$snapshot_time_age_limit.' min), will continue...'; echo "\n"; output_json($last_status['step']+1); } if($snapshot_status !== 'complete' and $snapshot_time_age <= $snapshot_time_age_limit){ echo 'snapshot has not complete, will waiting until it complete'; echo "\n"; die(); } if($snapshot_status == 'complete' and $snapshot_time_age > $snapshot_time_age_limit){ echo 'snapshot has complete but too old, will create a new snapshot'; echo "\n"; // create snapshot echo 'Create SNAPSHOT...'; echo "\n"; $data = "SUBID=".$first_server_id; $get_data = callAPI('POST', 'https://api.vultr.com/v1/snapshot/create', $data); $response = json_decode($get_data, true); $snapshot_id = $response['SNAPSHOTID']; echo 'SNAPSHOT ID: '.$snapshot_id; echo "\n"; echo 'Snapshot is processing, run again to continue...'.$snapshot_id; echo "\n"; die(); } break; |
步骤三:创建新服务器
注意高亮行,设置新创建服务器的区域、套餐、操作系统,下值为法兰克福、5美元、Debian 9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
case 2: echo 'Create new server...'; echo "\n"; // create new server if($server_count <= 1){ echo 'Creating New Server...'; echo "\n"; $data_array = array( "DCID" => 9, "VPSPLANID" => 201, "OSID" => 244 ); $get_data = callAPI('POST', 'https://api.vultr.com/v1/server/create', $data_array); $response = json_decode($get_data, true); echo 'New Server has Created(id='.$response['SUBID'].')'; echo "\n"; output_json($last_status['step']+1); } break; |
步骤四:从快照恢复
快照恢复需要等待,此步骤会进行状态判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
case 3: echo 'Restore snapshot...'; echo "\n"; // get snapshot list $get_data = callAPI('GET', 'https://api.vultr.com/v1/snapshot/list', false); $response = json_decode($get_data, true); $first_snapshot_id = array_key_first($response); $first_snapshot_info = $response[array_key_first($response)]; echo 'snapshot ID: '.$first_snapshot_id; echo "\n"; $snapshot_status = $first_snapshot_info['status']; echo 'snapshot Status: '.$snapshot_status; echo "\n"; echo 'snapshot create date: '.date("Y-m-d H:i:s",strtotime($first_snapshot_info['date_created'])+8*60*60); echo "\n"; $snapshot_time_age = ((time()-(strtotime($first_snapshot_info['date_created'])+8*60*60))/60); echo 'snapshot time ago: '.$snapshot_time_age.' min'; echo "\n"; if($snapshot_status == 'complete' and $last_server_info['power_status'] == 'running'){ $data_array = array( "SUBID" => $last_server_id, "SNAPSHOTID" => $first_snapshot_id ); $get_data = callAPI('POST', 'https://api.vultr.com/v1/server/restore_snapshot', $data_array); $response = json_decode($get_data, true); if($response == NULL){ echo 'New Server has not installed'; echo "\n"; die(); } var_dump($response); echo "\n"; output_json($last_status['step']+1); }else{ echo 'snapshot status: '.$snapshot_status.' server status: '.$last_server_info['power_status']; echo "\n"; } break; |
步骤五:删除旧服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
case 4: echo 'Destroy old server...'; echo "\n"; // Destroy old server if($last_server_info['power_status'] == 'stopped'){ echo 'server restore not complete, waiting for restore complete...'; echo "\n"; } elseif($server_count > 1){ echo 'Old server(id='.$first_server_id.') exists, will destroy old one'; echo "\n"; echo 'Destroy old server(id='.$first_server_id.')...'; echo "\n"; $data = "SUBID=$first_server_id"; $get_data = callAPI('POST', 'https://api.vultr.com/v1/server/destroy', $data); var_dump($get_data); if($get_data = 200){ echo 'Destroy Server(ID='.$first_server_id.') Succeed'; echo "\n"; } output_json($last_status['step']+1); }else{ echo 'only one server, destroy process not running, skip destroy process...'; echo "\n"; output_json($last_status['step']+1); } break; |
以上更换主机的步骤已全部结束,以下为附加功能
步骤六:更新WireGuard节点信息,Ping节点
此步骤ping阶段会进入假死,需要手动ctrl+c结束,目前使用ini_set可以解决这个问题。
替换高亮部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
case 5: ini_set('max_execution_time', '5'); echo 'Set Wireguard peer info...'; echo "\n"; echo 'Set Wireguard peer ip to: '.$last_server_info['main_ip']; echo "\n"; $cmd_set_wireguard = "wg set wg0 peer your_peer_public_key endpoint ".$last_server_info['main_ip'].":your_peer_port"; exec($cmd_set_wireguard,$array); echo 'Ping Peer...'; echo "\n"; $cmd_ping_peer = "ping your_peer_local_ip"; exec($cmd_ping_peer,$array); output_json($last_status['step']+1); break; |
循环步骤:检查TCP状态
替换高亮行,TCP检测用的是百度的网站robot检测api,连百度都访问不了,那么肯定是被玩坏了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
default: $max_tcp_test_time = 3; $cmd_wget = "wget -q -O - https://ziyuan.baidu.com/robots/doanalysis?site=your_domain_name"; exec($cmd_wget,$array); $wget_result = json_decode($array[0],true); $tcp_test = $wget_result['status']; if($tcp_test == 9){ echo "tcp test result is $tcp_test, block by chinafirewall, will test again"; echo "\n"; echo "current test time is ".(0-$last_status['step']).", max test time is $max_tcp_test_time"; echo "\n"; if($last_status['step'] > 0){ output_json(-1); }else{ output_json($last_status['step']-1); } if($last_status['step'] <= -($max_tcp_test_time)){ echo "tcp test Reached the max test time: $max_tcp_test_time, will start swap ip process..."; echo "\n"; output_json(0); } }else{ echo "tcp test result is $tcp_test, server is running fine"; echo "\n"; } |
There are no comments yet