之前和几个客户交流都提到希望快速频繁变更服务器的公网地址,想到的有几种方案来做:
1. 如果 VM 使用实例级公网 IP,可以通过 SDK 动态调整 VM NIC 上所关联的公网 IP 地址;
2. 如果 VM 使用 SLB 的公网 IP,可以通过 SDK 动态调整 SLB 的 公网 IP;
但是上述方法都有些不足之处,方法 1 如果客户有大量 VM,客户需要开销大量的实例级公网 IP,成本不优化;方法 2 SLB 的公网地址被身后的 VM 组共享,切换一个 IP 会同时影响多台虚拟机。另外两种方案都会有相对的 IP 地址变更时间开销过长的问题。如何在成本和时效性下进行优化,今天我们就以上述需求作为一个场景,用 Serverless 的方式来达成最终目标。基于成本和时间的诉求,Azure Container Instance 服务是个不错的选择,首先 ACI 服务是一个完全托管的容器服务,客户不需要搭建和维护容器计算集群(如用 VM 去搭建 Swarm 或者 Kubernetes ),客户管理视图和颗粒度在容器,可以直接进行容器消费,创建删除容器,并且按照容器分配的 CPU 和 内存资源的占用时间进行收费,并且容器生命周期跳转是非常快速的,可以快速的删除、创建容器。所以从成本和时效上都可以满足上述要求,第 3 个方案也呼之欲出,将应用运行在 ACI 容器中,当需要做公网 IP 地址变更的时候将现有容器回收后重新创建新的容器。变更的方案有了,但什么时候变更,如何监测并触发变更?通常的做法会有一个带外的监控程序来检测记录需要变更的公网 IP 并触发变更,这部分需要调试并维护一部分代码。为了简化这部分实现,通过采用 Azure Logic Apps 服务来实现 Codeless,只需要维护简单的事件逻辑以及因果关系就可实现上述公网地址触发变更的目标。Azure Logic Apps 内置的百余种服务连接器可以实现 Codeless 或 Lesscode 服务间的联动调用。
下面我们先看一下方案 3 的架构设计图:
图中 Azure Logic Apps 暴露一个 Http 访问终结点作为触发器( Trigger ),事件触发由 Azure Container Instance 创建的实例来完成,Azure Container Instance 支持容器的重启策略设置(restart policy) ,容器在创建时将重启策略设置为永不(Never),当容器内执行的程序发现需要进行公网地址变更时终止容器内的执行程序返回 ExitCode 0,此时容器会进入 Terminated,在返回 ExitCode 前在容器内通过 Call 前面 Logic Apps 暴露的触发终结点来触发 Logic Apps 执行,从而实现的 ServerLess + Codeless 的公网地址变更逻辑。当 Logic Apps 被触发后,通过内置的 Azure Container Instance 连接器,来实现判断需要变更 IP 的容器是否进入 Terminated 状态,如果进入则删除该容器实例并重新创建。
下面我们再来看一下 Azure Logic Apps 对于上述逻辑的实现图:
上述逻辑均可在 Azure Logic App 的 GUI 视图下的 Designer 工具中设计完成,这里不做赘述,基本的 Designer 使用方法可以参阅后面的参考资料。如果想偷懒,可以用下面的描述模板来做,具体的配置内容可以参见如下模板(里面的一些 id 信息需要根据部署环境进行调整):
1 { 2 "$connections": { 3 "value": { 4 "aci": { 5 "connectionId": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/resourceGroups/NSGNewFeature/providers/Microsoft.Web/connections/aci", 6 "connectionName": "aci", 7 "id": "/subscriptions/c04b3c63-8dfe-4f98-be18-e71ff67a1f4e/providers/Microsoft.Web/locations/westcentralus/managedApis/aci" 8 } 9 } 10 }, 11 "definition": { 12 "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 13 "actions": { 14 "Delay": { 15 "inputs": { 16 "interval": { 17 "count": 10, 18 "unit": "Second" 19 } 20 }, 21 "runAfter": {}, 22 "type": "Wait" 23 }, 24 "For_each": { 25 "actions": { 26 "Condition": { 27 "actions": { 28 "Create_container_group_2": { 29 "inputs": { 30 "body": { 31 "location": "eastus", 32 "properties": { 33 "containers": [ 34 { 35 "name": "@triggerBody()['containergroupname']", 36 "properties": { 37 "command": [ 38 "/bin/sh", 39 "-c", 40 "apt-get update && apt-get install dnsutils curl -y && i=1 && while [ $i -le 1 ]; do echo $(nslookup myip.opendns.com resolver1.opendns.com) >> /mnt/share1/logs; i=$(($i+1)); sleep 2; done && curl -H "$logicappheader" -X POST -d "{\"containergroupname\":\"$containergroupname\"}" --url $logicappurl" 41 ], 42 "environmentVariables": [ 43 { 44 "name": "containergroupname", 45 "value": "@triggerBody()['containergroupname']" 46 }, 47 { 48 "name": "logicappurl", 49 "value": "https://prod-18.westcentralus.logic.azure.com:443/workflows/be4b5a76ce7048b3bfd5de7b356a0df4/triggers/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=feVYrQXvrAbAgS2jP8yJ-xF4xxmKF4rAOt1I4-0yn3g" 50 }, 51 { 52 "name": "logicappheader", 53 "value": "Content-Type: application/json" 54 } 55 ], 56 "image": "ubuntu", 57 "resources": { 58 "requests": { 59 "cpu": 1, 60 "memoryInGB": 1 61 } 62 }, 63 "volumeMounts": [ 64 { 65 "mountPath": "/mnt/share1/", 66 "name": "acifileshare", 67 "readOnly": false 68 } 69 ] 70 } 71 } 72 ], 73 "osType": "Linux", 74 "restartPolicy": "Never", 75 "volumes": [ 76 { 77 "azureFile": { 78 "readOnly": false, 79 "shareName": "acifileshare", 80 "storageAccountKey": "togVcvkCmG5UpTO8yNUa87Y1EP/8fXoD7RraWiyxvUi8ds7dvcngOdyg+6wBxX85161ElsNdD0dBSItR9+/3dw==", 81 "storageAccountName": "acifileshare" 82 }, 83 "name": "acifileshare" 84 } 85 ] 86 } 87 }, 88 "host": { 89 "connection": { 90 "name": "@parameters('$connections')['aci']['connectionId']" 91 } 92 }, 93 "method": "put", 94 "path": "/subscriptions/@{encodeURIComponent('4507938f-a0ac-4571-978e-7cc741a60af8')}/resourceGroups/@{encodeURIComponent('aksdemo')}/providers/Microsoft.ContainerInstance/containerGroups/@{encodeURIComponent(triggerBody()['containergroupname'])}", 95 "queries": { 96 "x-ms-api-version": "2017-10-01-preview" 97 } 98 }, 99 "runAfter": { 100 "Delay_2": [ 101 "Succeeded" 102 ] 103 }, 104 "type": "ApiConnection" 105 }, 106 "Delay_2": { 107 "inputs": { 108 "interval": { 109 "count": 10, 110 "unit": "Second" 111 } 112 }, 113 "runAfter": { 114 "Delete_container_group_2": [ 115 "Succeeded" 116 ] 117 }, 118 "type": "Wait" 119 }, 120 "Delete_container_group_2": { 121 "inputs": { 122 "host": { 123 "connection": { 124 "name": "@parameters('$connections')['aci']['connectionId']" 125 } 126 }, 127 "method": "delete", 128 "path": "/subscriptions/@{encodeURIComponent('4507938f-a0ac-4571-978e-7cc741a60af8')}/resourceGroups/@{encodeURIComponent('aksdemo')}/providers/Microsoft.ContainerInstance/containerGroups/@{encodeURIComponent(triggerBody()['containergroupname'])}", 129 "queries": { 130 "x-ms-api-version": "2017-10-01-preview" 131 } 132 }, 133 "runAfter": {}, 134 "type": "ApiConnection" 135 } 136 }, 137 "else": {}, 138 "expression": "@equals(items('For_each')?['properties']?['instanceView']?['currentState']?['detailStatus'], 'Completed')", 139 "runAfter": {}, 140 "type": "If" 141 } 142 }, 143 "foreach": "@body('Get_properties_of_a_container_group')['properties']['containers']", 144 "runAfter": { 145 "Get_properties_of_a_container_group": [ 146 "Succeeded" 147 ] 148 }, 149 "type": "Foreach" 150 }, 151 "Get_properties_of_a_container_group": { 152 "inputs": { 153 "host": { 154 "connection": { 155 "name": "@parameters('$connections')['aci']['connectionId']" 156 } 157 }, 158 "method": "get", 159 "path": "/subscriptions/@{encodeURIComponent('4507938f-a0ac-4571-978e-7cc741a60af8')}/resourceGroups/@{encodeURIComponent('aksdemo')}/providers/Microsoft.ContainerInstance/containerGroups/@{encodeURIComponent(triggerBody()['containergroupname'])}", 160 "queries": { 161 "x-ms-api-version": "2017-10-01-preview" 162 } 163 }, 164 "runAfter": { 165 "Delay": [ 166 "Succeeded" 167 ] 168 }, 169 "type": "ApiConnection" 170 } 171 }, 172 "contentVersion": "1.0.0.0", 173 "outputs": {}, 174 "parameters": { 175 "$connections": { 176 "defaultValue": {}, 177 "type": "Object" 178 } 179 }, 180 "triggers": { 181 "manual": { 182 "inputs": { 183 "method": "POST", 184 "schema": { 185 "properties": { 186 "containergroupname": { 187 "type": "string" 188 } 189 }, 190 "required": [ 191 "containergroupname" 192 ], 193 "type": "object" 194 } 195 }, 196 "kind": "Http", 197 "type": "Request" 198 } 199 } 200 } 201 }
参考资料:
Azure Logic Apps Designer 使用方法:https://docs.microsoft.com/en-us/azure/logic-apps/tutorial-build-schedule-recurring-logic-app-workflow
Azure Logic Apps Connector 详细介绍:https://docs.microsoft.com/en-us/connectors/
Azure Container Instance 介绍:https://docs.microsoft.com/en-us/azure/templates/microsoft.containerinstance/containergroups