├── go.mod ├── go.sum ├── .gitignore ├── README.md ├── LICENSE ├── cmd └── aksip │ ├── main.go │ └── calculator.go └── .github └── workflows └── aksip.yaml /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cmendible/aksip 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/olekukonko/tablewriter v0.0.5 // indirect 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 2 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 3 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 4 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Ignore syso files 18 | *.syso 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![aksip](https://github.com/cmendible/aksip/workflows/aksip/badge.svg) 2 | 3 | # aksip 4 | 5 | Azure Kubernetes Service (AKS) advanced networking (CNI) address space calculator. 6 | 7 | ## Download 8 | 9 | * Download the the latest version from the [releases](https://github.com/cmendible/aksip/releases) page. 10 | 11 | ## Usage 12 | 13 | ``` shell 14 | aksip -h 15 | Usage of aksip: 16 | -l int 17 | Number of expected internal LoadBalancer services (default 1) 18 | -n int 19 | Number of nodes (default 3) 20 | -p int 21 | Max pods per node (default 30) 22 | -s int 23 | Number of scale nodes (default 1) 24 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Carlos Mendible 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/aksip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/olekukonko/tablewriter" 11 | ) 12 | 13 | func main() { 14 | 15 | nodes := flag.Int("n", 3, "Number of nodes") 16 | scale := flag.Int("s", 1, "Number of scale nodes") 17 | maxPods := flag.Int("p", 30, "Max pods per node") 18 | isvc := flag.Int("l", 1, "Number of expected internal LoadBalancer services") 19 | table := flag.Bool("t", false, "Set true for output table") 20 | 21 | flag.Parse() 22 | 23 | r := calculator{MaxPods: *maxPods, Nodes: *nodes, Scale: *scale, Isvc: *isvc} 24 | r.validate() 25 | r.calculateRequiredIPs() 26 | 27 | err := r.getCIDR() 28 | if err != nil { 29 | fmt.Println(err.Error()) 30 | os.Exit(1) 31 | } 32 | 33 | if !*table { 34 | renderJson(r) 35 | } else { 36 | renderTable(r) 37 | } 38 | 39 | os.Exit(0) 40 | } 41 | 42 | func renderTable(r calculator) { 43 | data := [][]string{ 44 | { 45 | strconv.Itoa(r.Nodes), 46 | strconv.Itoa(r.Scale), 47 | strconv.Itoa(r.MaxPods), 48 | strconv.Itoa(r.Isvc), 49 | strconv.Itoa(r.RequiredIPs), 50 | r.CIDR}, 51 | } 52 | 53 | table := tablewriter.NewWriter(os.Stdout) 54 | table.SetHeader([]string{"Nodes", "Scale", "maxPods", "isvc", "IPs", "CIDR"}) 55 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 56 | table.SetAlignment(tablewriter.ALIGN_RIGHT) 57 | table.SetColumnSeparator("") 58 | table.SetRowSeparator("") 59 | table.SetHeaderLine(false) 60 | table.SetBorder(false) 61 | 62 | for _, v := range data { 63 | table.Append(v) 64 | } 65 | table.Render() 66 | } 67 | 68 | func renderJson(r calculator) { 69 | j, _:= json.MarshalIndent(r, "", " ") 70 | fmt.Println(string(j)) 71 | } 72 | -------------------------------------------------------------------------------- /cmd/aksip/calculator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | type calculator struct { 11 | Nodes int `json:"nodes"` 12 | Scale int `json:"scale"` 13 | MaxPods int `json:"maxPods"` 14 | Isvc int `json:"isvc"` 15 | RequiredIPs int `json:"requiredIPs"` 16 | CIDR string `json:"cidr"` 17 | } 18 | 19 | func (r *calculator) validate() { 20 | if r.MaxPods > 250 { 21 | fmt.Println("Max pods is higher than 250 (Limit per node).") 22 | os.Exit(1) 23 | } 24 | 25 | if r.MaxPods < 10 { 26 | fmt.Println("Max pods is lower than 10 (Minimum per node).") 27 | os.Exit(1) 28 | } 29 | 30 | if r.MaxPods*r.Nodes < 30 { 31 | fmt.Println("Projected number of pods is lower than 30 (Minimum per 30 per cluster).") 32 | os.Exit(1) 33 | } 34 | 35 | if r.Nodes+r.Scale > 1000 { 36 | fmt.Println("Total number of nodes (nodes + scale) is higher than 1000 (Limit per cluster).") 37 | os.Exit(1) 38 | } 39 | } 40 | 41 | func (r *calculator) calculateRequiredIPs() { 42 | r.RequiredIPs = (r.Nodes + 1 + r.Scale) + ((r.Nodes + 1 + r.Scale) * r.MaxPods) + r.Isvc 43 | } 44 | 45 | func (r *calculator) getCIDR() error { 46 | cidrs := map[int]int{} 47 | 48 | // Azure smallest supported subnet size is /29 and biggest /8 49 | // https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#how-small-and-how-large-can-vnets-and-subnets-be 50 | for i := 29; i >= 8; i-- { 51 | cidrs[i] = int(getAvailableHosts(i)) 52 | } 53 | 54 | for cidr, ips := range cidrs { 55 | if ips > r.RequiredIPs && r.RequiredIPs > cidrs[cidr+1] { 56 | r.CIDR = fmt.Sprintf("/%s", strconv.Itoa(cidr)) 57 | return nil 58 | } 59 | } 60 | 61 | return fmt.Errorf("no CIDR found") 62 | } 63 | 64 | func getAvailableHosts(cidr int) float64 { 65 | // Remember Azure reserves 5 IPs in every subnet 66 | // https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets 67 | return math.Pow(2, float64(32-cidr)) - 5 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/aksip.yaml: -------------------------------------------------------------------------------- 1 | name: aksip 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | tags: 9 | - v* 10 | pull_request: 11 | branches: 12 | - master 13 | - release-* 14 | jobs: 15 | build: 16 | name: Build ${{ matrix.target_os }}_${{ matrix.target_arch }} binaries 17 | runs-on: ${{ matrix.os }} 18 | env: 19 | GOVER: 1.14 20 | GOOS: ${{ matrix.target_os }} 21 | GOARCH: ${{ matrix.target_arch }} 22 | GOPROXY: https://proxy.golang.org 23 | ARCHIVE_OUTDIR: dist/archives 24 | strategy: 25 | matrix: 26 | os: [ubuntu-latest, windows-latest, macOS-latest] 27 | target_arch: [amd64] 28 | include: 29 | - os: ubuntu-latest 30 | target_os: linux 31 | filename: aksip_linux_amd64 32 | - os: windows-latest 33 | target_os: windows 34 | filename: aksip_windows_amd64.exe 35 | - os: macOS-latest 36 | target_os: darwin 37 | filename: aksip_darwin_amd64 38 | steps: 39 | - name: Set up Go ${{ env.GOVER }} 40 | uses: actions/setup-go@v2.1.3 41 | with: 42 | go-version: ${{ env.GOVER }} 43 | - name: Check out code into the Go module directory 44 | uses: actions/checkout@v2 45 | - name: Create output folder 46 | run: | 47 | mkdir -p ${{ env.ARCHIVE_OUTDIR }}/${{ matrix.target_os }} 48 | - name: Run build and archive non windows binaries 49 | if: matrix.target_os != 'windows' 50 | run: | 51 | GOOS=${{ matrix.target_os }} GOARCH=${{ matrix.target_arch }} go build -ldflags "-s -w" -o ${{ env.ARCHIVE_OUTDIR }}/${{ matrix.target_os }}/${{ matrix.filename }} ./cmd/aksip/ 52 | - name: Run build and archive windows binaries 53 | if: matrix.target_os == 'windows' 54 | run: | 55 | go build -ldflags "-s -w" -o ${{ env.ARCHIVE_OUTDIR }}/${{ matrix.target_os }}/${{ matrix.filename }} ./cmd/aksip/ 56 | - name: upload artifacts 57 | uses: actions/upload-artifact@master 58 | with: 59 | name: aksip_${{ matrix.target_os }}_${{ matrix.target_arch }} 60 | path: ${{ env.ARCHIVE_OUTDIR }}/${{ matrix.target_os }} 61 | publish: 62 | name: Publish binaries 63 | needs: build 64 | if: github.event_name != 'pull_request' 65 | env: 66 | ARTIFACT_DIR: ./release 67 | runs-on: ubuntu-latest 68 | steps: 69 | - name: Check out code into the Go module directory 70 | uses: actions/checkout@v2 71 | - name: download artifacts - aksip_linux_amd64 72 | uses: actions/download-artifact@master 73 | with: 74 | name: aksip_linux_amd64 75 | path: ${{ env.ARTIFACT_DIR }} 76 | - name: download artifacts - aksip_windows_amd64 77 | uses: actions/download-artifact@master 78 | with: 79 | name: aksip_windows_amd64 80 | path: ${{ env.ARTIFACT_DIR }} 81 | - name: download artifacts - aksip_darwin_amd64 82 | uses: actions/download-artifact@master 83 | with: 84 | name: aksip_darwin_amd64 85 | path: ${{ env.ARTIFACT_DIR }} 86 | - name: publish binaries to github 87 | if: startswith(github.ref, 'refs/tags/v') 88 | run: | 89 | echo "installing github-release-cli..." 90 | sudo npm install --silent --no-progress -g @babel/runtime 91 | sudo npm install --silent --no-progress -g github-release-cli 92 | 93 | # Get the list of files 94 | RELEASE_ARTIFACT=(${ARTIFACT_DIR}/*) 95 | 96 | # Parse repository to get owner and repo names 97 | OWNER_NAME="${GITHUB_REPOSITORY%%/*}" 98 | REPO_NAME="${GITHUB_REPOSITORY#*/}" 99 | 100 | REL_VERSION="0.2.0" 101 | RELEASE_BODY="This is the release candidate ${REL_VERSION}" 102 | 103 | export GITHUB_TOKEN=${{ secrets.AKSIP_BOT_TOKEN }} 104 | echo "Uploading aksip Binaries to GitHub Release" 105 | github-release upload \ 106 | --owner $OWNER_NAME --repo $REPO_NAME \ 107 | --tag "v${REL_VERSION}" \ 108 | --release-name "aksip v${REL_VERSION}" \ 109 | --body "${RELEASE_BODY}" \ 110 | --prerelease true \ 111 | ${RELEASE_ARTIFACT[*]} --------------------------------------------------------------------------------