|
| 1 | +# Note: you must have at least one hub cluster and one member cluster. |
| 2 | +# |
| 3 | +# The reason we require the hub cluster API URL as an argument is that, in some environments (e.g. kind), the URL cannot be derived from kubeconfig and needs to be explicitly provided by users. |
| 4 | +# |
| 5 | +# For example, using Docker, you can get the right IP address for member clusters to use: |
| 6 | +# docker inspect local-hub-01-control-plane --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' |
| 7 | +# |
| 8 | +# You can assume port 6443 unless you have explicitly changed the API server port for your hub cluster. Don't use the mapped Docker port (i.e. 50063) |
| 9 | +# |
| 10 | +# Example usage: |
| 11 | +# ./join-member-clusters.ps1 0.2.2 demo-hub-01 https://172.18.0.2:6443 member-cluster-1 member-cluster-2 |
| 12 | + |
| 13 | +[CmdletBinding()] |
| 14 | +param( |
| 15 | + [Parameter(Position = 0)] |
| 16 | + [string]$KubefleetVersion, |
| 17 | + |
| 18 | + [Parameter(Position = 1)] |
| 19 | + [string]$HubClusterName, |
| 20 | + |
| 21 | + [Parameter(Position = 2)] |
| 22 | + [string]$HubControlPlaneURL, |
| 23 | + |
| 24 | + [Parameter(Position = 3, ValueFromRemainingArguments = $true)] |
| 25 | + [string[]]$MemberClusterNames, |
| 26 | + |
| 27 | + [switch]$Help |
| 28 | +) |
| 29 | + |
| 30 | +function Show-Usage { |
| 31 | + @' |
| 32 | +Usage: |
| 33 | + ./join-member-clusters.ps1 <kubefleet-version> <hub-cluster-name> <hub-control-plane-url> <member-cluster-name-1> [<member-cluster-name-2> ...] |
| 34 | +
|
| 35 | +Example: |
| 36 | + ./join-member-clusters.ps1 0.2.2 demo-hub-01 https://172.18.0.2:6443 member-cluster-1 member-cluster-2 |
| 37 | +
|
| 38 | +Requirements: |
| 39 | + - PowerShell 7, kubectl, and helm must be installed |
| 40 | + - hub and member cluster names must exist in your kubeconfig |
| 41 | +'@ |
| 42 | +} |
| 43 | + |
| 44 | +function Fail-WithHelp { |
| 45 | + param( |
| 46 | + [Parameter(Mandatory = $true)] |
| 47 | + [string]$Message |
| 48 | + ) |
| 49 | + |
| 50 | + Write-Error $Message |
| 51 | + Write-Host "" |
| 52 | + Show-Usage |
| 53 | + exit 1 |
| 54 | +} |
| 55 | + |
| 56 | +function Test-CommandExists { |
| 57 | + param( |
| 58 | + [Parameter(Mandatory = $true)] |
| 59 | + [string]$Name |
| 60 | + ) |
| 61 | + |
| 62 | + return $null -ne (Get-Command $Name -ErrorAction SilentlyContinue) |
| 63 | +} |
| 64 | + |
| 65 | +function Test-ClusterExists { |
| 66 | + param( |
| 67 | + [Parameter(Mandatory = $true)] |
| 68 | + [string]$Name |
| 69 | + ) |
| 70 | + |
| 71 | + $clusters = @(kubectl config get-clusters 2>$null) |
| 72 | + if ($LASTEXITCODE -ne 0) { |
| 73 | + return $false |
| 74 | + } |
| 75 | + |
| 76 | + return $clusters | Select-Object -Skip 1 | Where-Object { $_.Trim() -eq $Name } | ForEach-Object { $true } | Select-Object -First 1 |
| 77 | +} |
| 78 | + |
| 79 | +if ($Help) { |
| 80 | + Show-Usage |
| 81 | + exit 0 |
| 82 | +} |
| 83 | + |
| 84 | +if ([string]::IsNullOrWhiteSpace($KubefleetVersion) -or [string]::IsNullOrWhiteSpace($HubClusterName) -or [string]::IsNullOrWhiteSpace($HubControlPlaneURL) -or $null -eq $MemberClusterNames -or $MemberClusterNames.Count -lt 1) { |
| 85 | + $argumentCount = 0 |
| 86 | + if (-not [string]::IsNullOrWhiteSpace($KubefleetVersion)) { |
| 87 | + $argumentCount++ |
| 88 | + } |
| 89 | + if (-not [string]::IsNullOrWhiteSpace($HubClusterName)) { |
| 90 | + $argumentCount++ |
| 91 | + } |
| 92 | + if (-not [string]::IsNullOrWhiteSpace($HubControlPlaneURL)) { |
| 93 | + $argumentCount++ |
| 94 | + } |
| 95 | + if ($null -ne $MemberClusterNames) { |
| 96 | + $argumentCount += $MemberClusterNames.Count |
| 97 | + } |
| 98 | + |
| 99 | + Fail-WithHelp "expected at least 4 arguments, got $argumentCount" |
| 100 | +} |
| 101 | + |
| 102 | +if (-not (Test-CommandExists -Name kubectl)) { |
| 103 | + Fail-WithHelp "kubectl is not installed or not available in PATH" |
| 104 | +} |
| 105 | + |
| 106 | +if (-not (Test-CommandExists -Name helm)) { |
| 107 | + Fail-WithHelp "helm is not installed or not available in PATH" |
| 108 | +} |
| 109 | + |
| 110 | +if (-not (Test-ClusterExists -Name $HubClusterName)) { |
| 111 | + Fail-WithHelp "hub cluster '$HubClusterName' was not found in kubeconfig" |
| 112 | +} |
| 113 | + |
| 114 | +[System.Uri]$parsedHubURL = $null |
| 115 | +if (-not [System.Uri]::TryCreate($HubControlPlaneURL, [System.UriKind]::Absolute, [ref]$parsedHubURL)) { |
| 116 | + Fail-WithHelp "hub control plane URL '$HubControlPlaneURL' is not a valid absolute URL" |
| 117 | +} |
| 118 | + |
| 119 | +if ($parsedHubURL.Scheme -ne "https") { |
| 120 | + Fail-WithHelp "hub control plane URL must use https" |
| 121 | +} |
| 122 | + |
| 123 | +foreach ($memberClusterName in $MemberClusterNames) { |
| 124 | + if ([string]::IsNullOrWhiteSpace($memberClusterName)) { |
| 125 | + Fail-WithHelp "member cluster name cannot be empty" |
| 126 | + } |
| 127 | + |
| 128 | + if ($memberClusterName -eq $HubClusterName) { |
| 129 | + Fail-WithHelp "member cluster '$memberClusterName' cannot be the same as hub cluster" |
| 130 | + } |
| 131 | + |
| 132 | + if (-not (Test-ClusterExists -Name $memberClusterName)) { |
| 133 | + Fail-WithHelp "member cluster '$memberClusterName' was not found in kubeconfig" |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +foreach ($memberClusterName in $MemberClusterNames) { |
| 138 | + # Note that Fleet will recognize your cluster with this name once it joins. |
| 139 | + $serviceAccount = "$memberClusterName-hub-cluster-access" |
| 140 | + $serviceAccountSecret = "$memberClusterName-hub-cluster-access-token" |
| 141 | + |
| 142 | + Write-Host "Switching into hub cluster context..." |
| 143 | + kubectl config use-context $HubClusterName |
| 144 | + |
| 145 | + # The service account can, in theory, be created in any namespace; for simplicity reasons, |
| 146 | + # here you will use the namespace reserved by Fleet installation, `fleet-system`. |
| 147 | + # |
| 148 | + # Note that if you choose a different value, commands in some steps below need to be |
| 149 | + # modified accordingly. |
| 150 | + Write-Host "Creating member service account..." |
| 151 | + kubectl create serviceaccount $serviceAccount -n fleet-system |
| 152 | + |
| 153 | + Write-Host "Creating member service account secret..." |
| 154 | + @" |
| 155 | +apiVersion: v1 |
| 156 | +kind: Secret |
| 157 | +metadata: |
| 158 | + name: $serviceAccountSecret |
| 159 | + namespace: fleet-system |
| 160 | + annotations: |
| 161 | + kubernetes.io/service-account.name: $serviceAccount |
| 162 | +type: kubernetes.io/service-account-token |
| 163 | +"@ | kubectl apply -f - |
| 164 | + |
| 165 | + Write-Host "Creating member cluster custom resource on hub cluster..." |
| 166 | + $encodedToken = kubectl get secret $serviceAccountSecret -n fleet-system -o "jsonpath={.data.token}" |
| 167 | + $token = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encodedToken)) |
| 168 | + |
| 169 | + @" |
| 170 | +apiVersion: cluster.kubernetes-fleet.io/v1 |
| 171 | +kind: MemberCluster |
| 172 | +metadata: |
| 173 | + name: $memberClusterName |
| 174 | +spec: |
| 175 | + identity: |
| 176 | + name: $memberClusterName-hub-cluster-access |
| 177 | + kind: ServiceAccount |
| 178 | + namespace: fleet-system |
| 179 | + apiGroup: "" |
| 180 | + heartbeatPeriodSeconds: 15 |
| 181 | +"@ | kubectl apply -f - |
| 182 | + |
| 183 | + # Install the member agent helm chart on the member cluster. |
| 184 | + Write-Host "Switching to member cluster context..." |
| 185 | + kubectl config use-context $memberClusterName |
| 186 | + |
| 187 | + # Create the secret with the token extracted previously for member agent to use. |
| 188 | + Write-Host "Creating secret..." |
| 189 | + kubectl delete secret hub-kubeconfig-secret |
| 190 | + kubectl create secret generic hub-kubeconfig-secret --from-literal="token=$token" |
| 191 | + |
| 192 | + Write-Host "Uninstalling any existing member-agent instances..." |
| 193 | + helm uninstall member-agent -n fleet-system --wait |
| 194 | + |
| 195 | + Write-Host "Installing member-agent..." |
| 196 | + helm install member-agent oci://ghcr.io/kubefleet-dev/kubefleet/charts/member-agent ` |
| 197 | + --version $KubefleetVersion ` |
| 198 | + --set "config.hubURL=$HubControlPlaneURL" ` |
| 199 | + --set "config.memberClusterName=$memberClusterName" ` |
| 200 | + --set logFileMaxSize=100000 ` |
| 201 | + --namespace fleet-system ` |
| 202 | + --create-namespace |
| 203 | + |
| 204 | + kubectl get pods -A |
| 205 | + kubectl config use-context $HubClusterName |
| 206 | + kubectl get membercluster $memberClusterName |
| 207 | +} |
0 commit comments