Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit 767932c

Browse files
tamilmani1989jackfrancis
authored andcommitted
Improving IP address assignment for master nodes with Azure CNI. (#1966)
* Azure cni static ip change (#1) * VSTS#1828538 Modified master IPs from dynamic to static and made agent nic dependent on master nic * Modified firstconsecutive static IP for default vnet * updated documentation specific to Azure CNI * Fixed styling test errors * Handled no master scenario * updated one of the examples cluster config to use firstConsecutiveStaticIp from the start of subnet * removed azureCNI check so that agent nic will always depend on master nic * Specified dependency of agent nic on master nic in windows agents template * moving firstConsecutiveStaticIP away from the edge of usable address space * Update documentation to help customers how to specify firstConsecutiveStaticIP and ipAddressCount for master nodes.
1 parent cdd2832 commit 767932c

11 files changed

+91
-41
lines changed

docs/kubernetes/features.md

+3-17
Original file line numberDiff line numberDiff line change
@@ -167,29 +167,15 @@ Per default Calico still allows all communication within the cluster. Using Kube
167167

168168
## Custom VNET
169169

170-
ACS Engine supports deploying into an existing VNET. Operators must specify the ARM path/id of Subnets for the `masterProfile` and any `agentPoolProfiles`, as well as the first IP address to use for IP static IP allocation in `firstConsecutiveStaticIP`. Additionally, to prevent source address NAT'ing within the VNET, we assign to the `vnetCidr` property in `masterProfile` the CIDR block that represents the usable address space in the existing VNET.
170+
ACS Engine supports deploying into an existing VNET. Operators must specify the ARM path/id of Subnets for the `masterProfile` and any `agentPoolProfiles`, as well as the first IP address to use for static IP allocation in `firstConsecutiveStaticIP`. Please note that in any azure subnet, the first four and the last ip address is reserved and can not be used. Additionally, each POD now gets the IP address from the Subnet. As a result, for the master nodes, enough IP addresses (equal to `ipAddressCount`) should be available beyond `firstConsecutiveStaticIP`. By default, the `ipAddressCount` has a value of 30, and can be changed if desired. Furthermore, to prevent source address NAT'ing within the VNET, we assign to the `vnetCidr` property in `masterProfile` the CIDR block that represents the usable address space in the existing VNET.
171171

172-
Depending upon the size of the VNET address space, during deployment, it is possible to experience IP address assignment collision between the required Kubernetes static IPs (one each per master and one for the API server load balancer, if more than one masters) and Azure CNI-assigned dynamic IPs (one for each NIC on the agent nodes). In practice, the larger the VNET the less likely this is to happen; some detail, and then a guideline.
173-
174-
First, the detail:
175-
176-
* Azure CNI assigns dynamic IP addresses from the "beginning" of the subnet IP address space (specifically, it looks for available addresses starting at ".4" ["10.0.0.4" in a "10.0.0.0/24" network])
177-
* acs-engine will require a range of up to 16 unused IP addresses in multi-master scenarios (1 per master for up to 5 masters, and then the next 10 IP addresses immediately following the "last" master for headroom reservation, and finally 1 more for the load balancer immediately adjacent to the afore-described _n_ masters+10 sequence) to successfully scaffold the network stack for your cluster
178-
179-
A guideline that will remove the danger of IP address allocation collision during deployment:
180-
181-
* If possible, assign to the `firstConsecutiveStaticIP` configuration property an IP address that is near the "end" of the available IP address space in the desired subnet.
182-
* For example, if the desired subnet is a `/24`, choose the "239" address in that network space
183-
184-
In larger subnets (e.g., `/16`) it's not as practically useful to push static IP assignment to the very "end" of large subnet, but as long as it's not in the "first" `/24` (for example) your deployment will be resilient to this edge case behavior.
185-
186-
Before provisioning, modify the `masterProfile` and `agentPoolProfiles` to match the above requirements, with the below being a representative example:
172+
See below profiles as an example:
187173

188174
```json
189175
"masterProfile": {
190176
...
191177
"vnetSubnetId": "/subscriptions/SUB_ID/resourceGroups/RG_NAME/providers/Microsoft.Network/virtualNetworks/VNET_NAME/subnets/MASTER_SUBNET_NAME",
192-
"firstConsecutiveStaticIP": "10.239.255.239",
178+
"firstConsecutiveStaticIP": "10.239.0.5",
193179
"vnetCidr": "10.239.0.0/16",
194180
...
195181
},

examples/e2e-tests/kubernetes/release/default/definition.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"vmSize": "Standard_D2_v2",
1414
"OSDiskSizeGB": 200,
1515
"vnetSubnetId": "/subscriptions/SUB_ID/resourceGroups/RG_NAME/providers/Microsoft.Network/virtualNetworks/VNET_NAME/subnets/SUBNET_NAME",
16-
"firstConsecutiveStaticIP": "10.239.255.239",
16+
"firstConsecutiveStaticIP": "10.239.0.4",
1717
"vnetCidr": "10.239.0.0/16"
1818
},
1919
"agentPoolProfiles": [

examples/vnet/kubernetesvnet-azure-cni.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"dnsPrefix": "test",
1010
"vmSize": "Standard_D2_v2",
1111
"vnetSubnetId": "/subscriptions/SUB_ID/resourceGroups/RG_NAME/providers/Microsoft.Network/virtualNetworks/VNET_NAME/subnets/SUBNET_NAME",
12-
"firstConsecutiveStaticIP": "10.239.255.239",
12+
"firstConsecutiveStaticIP": "10.239.255.10",
1313
"vnetCidr": "10.239.0.0/16"
1414
},
1515
"agentPoolProfiles": [

parts/k8s/kubernetesagentresourcesvmas.t

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"[variables('nsgID')]"
1010
{{else}}
1111
"[variables('vnetID')]"
12+
{{end}}
13+
{{range $masterOffset := loop 1 GetMasterCount}}
14+
,"[concat(variables('masterNICNamePrefix'), sub({{$masterOffset}}, 1))]"
1215
{{end}}
1316
],
1417
"location": "[variables('location')]",

parts/k8s/kubernetesagentvars.t

+1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@
2424
"{{.Name}}osImageSKU": "[parameters('{{.Name}}osImageSKU')]",
2525
"{{.Name}}osImagePublisher": "[parameters('{{.Name}}osImagePublisher')]",
2626
"{{.Name}}osImageVersion": "[parameters('{{.Name}}osImageVersion')]",
27+
"masterNICNamePrefix": "[concat(variables('orchestratorName'), '-master-', variables('nameSuffix'), '-', 'nic-')]",
2728

parts/k8s/kubernetesmasterresources.t

+4-3
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,14 @@
338338
}
339339
}
340340
{{if IsAzureCNI}}
341-
{{range $seq := loop 2 .MasterProfile.IPAddressCount}}
341+
{{range $seq := loop 1 .MasterProfile.IPAddressCount}}
342342
,
343343
{
344-
"name": "ipconfig{{$seq}}",
344+
"name": "[concat('ipconfig', add({{$seq}}, 1))]",
345345
"properties": {
346+
"privateIPAddress": "[variables('masterSecondaryAddrs')[add(mul(copyIndex(variables('masterOffset')), variables('ipAddressCount')), sub({{$seq}}, 1))]]",
346347
"primary": false,
347-
"privateIPAllocationMethod": "Dynamic",
348+
"privateIPAllocationMethod": "Static",
348349
"subnet": {
349350
"id": "[variables('vnetSubnetID')]"
350351
}

parts/k8s/kubernetesmastervars.t

+4
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
"orchestratorNameVersionTag": "{{.OrchestratorProfile.OrchestratorType}}:{{.OrchestratorProfile.OrchestratorVersion}}",
182182
{{if IsAzureCNI}}
183183
"allocateNodeCidrs": false,
184+
"ipAddressCount": {{.MasterProfile.IPAddressCount}},
184185
{{else}}
185186
"allocateNodeCidrs": true,
186187
{{end}}
@@ -267,6 +268,9 @@
267268
"[concat(variables('masterFirstAddrPrefix'), add(3, int(variables('masterFirstAddrOctet4'))))]",
268269
"[concat(variables('masterFirstAddrPrefix'), add(4, int(variables('masterFirstAddrOctet4'))))]"
269270
],
271+
{{if IsAzureCNI}}
272+
"masterSecondaryAddrs": [{{GetMasterSecondaryIP}}],
273+
{{end}}
270274
"masterEtcdServerPort": {{GetMasterEtcdServerPort}},
271275
"masterEtcdClientPort": {{GetMasterEtcdClientPort}},
272276
"masterEtcdPeerURLs":[

parts/k8s/kuberneteswinagentresourcesvmas.t

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
"[variables('nsgID')]"
1010
{{else}}
1111
"[variables('vnetID')]"
12+
{{end}}
13+
{{range $masterOffset := loop 1 GetMasterCount}}
14+
,"[concat(variables('masterNICNamePrefix'), sub({{$masterOffset}}, 1))]"
1215
{{end}}
1316
],
1417
"location": "[variables('location')]",

pkg/acsengine/const.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const (
1818
// DefaultNonMasqueradeCidr specifies the subnet that should not be masqueraded on host
1919
DefaultNonMasqueradeCidr = "10.0.0.0/8"
2020
// DefaultFirstConsecutiveKubernetesStaticIP specifies the static IP address on Kubernetes master 0
21-
DefaultFirstConsecutiveKubernetesStaticIP = "10.240.255.5"
21+
DefaultFirstConsecutiveKubernetesStaticIP = "10.240.0.4"
2222
// DefaultAgentSubnetTemplate specifies a default agent subnet
2323
DefaultAgentSubnetTemplate = "10.%d.0.0/16"
2424
// DefaultKubernetesSubnet specifies the default subnet used for all masters, agents and pods

pkg/acsengine/defaults.go

+12-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package acsengine
22

33
import (
4+
"encoding/binary"
45
"fmt"
56
"net"
67

@@ -476,9 +477,6 @@ func setMasterNetworkDefaults(a *api.Properties) {
476477

477478
// Set the default number of IP addresses allocated for masters.
478479
if a.MasterProfile.IPAddressCount == 0 {
479-
// Allocate one IP address for the node.
480-
a.MasterProfile.IPAddressCount = 1
481-
482480
// Allocate IP addresses for pods if VNET integration is enabled.
483481
if a.OrchestratorProfile.IsAzureCNI() {
484482
if a.OrchestratorProfile.OrchestratorType == api.Kubernetes {
@@ -676,27 +674,23 @@ func certsAlreadyPresent(c *api.CertificateProfile, m int) map[string]bool {
676674

677675
// getFirstConsecutiveStaticIPAddress returns the first static IP address of the given subnet.
678676
func getFirstConsecutiveStaticIPAddress(subnetStr string) string {
679-
_, subnet, err := net.ParseCIDR(subnetStr)
677+
ip, _, err := net.ParseCIDR(subnetStr)
680678
if err != nil {
681679
return DefaultFirstConsecutiveKubernetesStaticIP
682680
}
683681

684-
// Find the first and last octet of the host bits.
685-
ones, bits := subnet.Mask.Size()
686-
firstOctet := ones / 8
687-
lastOctet := bits/8 - 1
688-
689-
// Set the remaining host bits in the first octet.
690-
subnet.IP[firstOctet] |= (1 << byte((8 - (ones % 8)))) - 1
691-
692-
// Fill the intermediate octets with 1s and last octet with offset. This is done so to match
693-
// the existing behavior of allocating static IP addresses from the last /24 of the subnet.
694-
for i := firstOctet + 1; i < lastOctet; i++ {
695-
subnet.IP[i] = 255
682+
ipv4 := ip.To4()
683+
if ipv4 == nil {
684+
return DefaultFirstConsecutiveKubernetesStaticIP
696685
}
697-
subnet.IP[lastOctet] = DefaultKubernetesFirstConsecutiveStaticIPOffset
698686

699-
return subnet.IP.String()
687+
ipint := binary.BigEndian.Uint32(ipv4)
688+
689+
// First 4 IPs will be reserved in Azure and cannot use.
690+
ipint += 4
691+
startIP := make(net.IP, 4)
692+
binary.BigEndian.PutUint32(startIP, ipint)
693+
return startIP.String()
700694
}
701695

702696
func getAddonsIndexByName(addons []api.KubernetesAddon, name string) int {

pkg/acsengine/engine.go

+58
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"bytes"
55
"compress/gzip"
66
"encoding/base64"
7+
"encoding/binary"
78
"encoding/json"
89
"errors"
910
"fmt"
1011
"hash/fnv"
1112
"io/ioutil"
1213
"log"
1314
"math/rand"
15+
"net"
1416
"net/http"
1517
"regexp"
1618
"runtime/debug"
@@ -875,6 +877,62 @@ func (t *TemplateGenerator) getTemplateFuncMap(cs *api.ContainerService) templat
875877
}
876878
return strings.TrimSuffix(buf.String(), ", ")
877879
},
880+
"GetMasterCount": func() int {
881+
masterProfile := cs.Properties.MasterProfile
882+
if masterProfile == nil {
883+
return 0
884+
}
885+
return masterProfile.Count
886+
},
887+
"GetMasterSecondaryIP": func() string {
888+
var ips string
889+
var ipint uint32
890+
891+
profile := cs.Properties.MasterProfile
892+
if profile == nil {
893+
return ""
894+
}
895+
896+
ip := net.ParseIP(profile.FirstConsecutiveStaticIP)
897+
ipv4 := ip.To4()
898+
if ipv4 != nil {
899+
ipint = binary.BigEndian.Uint32(ipv4)
900+
} else {
901+
log.Fatalf("Net IP To4() function returns nil")
902+
}
903+
904+
// Make space for first few IPs.
905+
ipint += uint32(profile.Count) + uint32(DefaultInternalLbStaticIPOffset) - 1
906+
startIP := make(net.IP, 4)
907+
binary.BigEndian.PutUint32(startIP, ipint)
908+
909+
totalIPCount := profile.Count * profile.IPAddressCount
910+
ipint = 0
911+
912+
// Generate All secondary IPs that master will use.
913+
for count := totalIPCount; count > 0; count-- {
914+
ipv4 = startIP.To4()
915+
if ipv4 != nil {
916+
ipint = binary.BigEndian.Uint32(ipv4)
917+
} else {
918+
log.Fatalf("Net IP To4() function returns nil for startIP")
919+
}
920+
921+
ipint++
922+
newIP := make(net.IP, 4)
923+
binary.BigEndian.PutUint32(newIP, ipint)
924+
925+
if ips != "" {
926+
ips = ips + "," + "\"" + newIP.String() + "\""
927+
} else {
928+
ips = "\"" + newIP.String() + "\""
929+
}
930+
931+
startIP = newIP
932+
}
933+
934+
return ips
935+
},
878936
"RequiresFakeAgentOutput": func() bool {
879937
return cs.Properties.OrchestratorProfile.OrchestratorType == api.Kubernetes
880938
},

0 commit comments

Comments
 (0)