Skip to content
This repository has been archived by the owner on Oct 24, 2023. It is now read-only.

feat: add support for private registry #523

Merged
merged 2 commits into from
Feb 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/topics/kubernetes-developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ For VERSION, we recommend that you provide a value which would help you identify
}
```

* If the container registry is private, for example Azure Container Registry, then provide the name of the private Azure registry along with the custom hyperkube image like this:

```
"kubernetesConfig": {
"customHyperkubeImage": "<your-private-registry>/hyperkube-amd64:<your-custom-version>",
"privateAzureRegistryServer": "<your-private-registry>"
}
```
NOTE: Make sure the service principal provided to run `aks-engine deploy` has access to pull images from this private registry. https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal#use-an-existing-service-principal

* AKS Engine uses the `aks-ubuntu-1604` image for the master and Linux agents. This image contains prebuilt versions of kubelet and kubectl, thus the provision scripts will not copy the desired kubelet binary ( and kubectl ) from the custom hyperkube image. In order to get over this limitation it is required to specify `ubuntu` for the master and Linux agents in "masterProfile" and each agent pool under `agentPoolProfiles`.

```
Expand Down
3 changes: 3 additions & 0 deletions parts/k8s/kubernetesinstalls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ installKubeletAndKubectl() {
pullContainerImage() {
CLI_TOOL=$1
DOCKER_IMAGE_URL=$2
if [ ! -z "${PRIVATE_AZURE_REGISTRY_SERVER}" ]; then
$CLI_TOOL login -u $SERVICE_PRINCIPAL_CLIENT_ID -p $SERVICE_PRINCIPAL_CLIENT_SECRET $PRIVATE_AZURE_REGISTRY_SERVER
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fi
retrycmd_if_failure 60 1 1200 $CLI_TOOL pull $DOCKER_IMAGE_URL || exit $ERR_CONTAINER_IMG_PULL_TIMEOUT
}

Expand Down
2 changes: 1 addition & 1 deletion parts/k8s/kubernetesmastervars.t
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"mountetcdScript": "{{GetKubernetesB64Mountetcd}}",
"customSearchDomainsScript": "{{GetKubernetesB64CustomSearchDomainsScript}}",
"sshdConfig": "{{GetB64sshdConfig}}",
"provisionScriptParametersCommon": "[concat('ADMINUSER=',parameters('linuxAdminUsername'),' ETCD_DOWNLOAD_URL=',parameters('etcdDownloadURLBase'),' ETCD_VERSION=',parameters('etcdVersion'),' CONTAINERD_VERSION=',parameters('containerdVersion'),' MOBY_VERSION=',parameters('mobyVersion'),' DOCKER_ENGINE_REPO=',parameters('dockerEngineDownloadRepo'),' TENANT_ID=',variables('tenantID'),' KUBERNETES_VERSION={{.OrchestratorProfile.OrchestratorVersion}} HYPERKUBE_URL=',parameters('kubernetesHyperkubeSpec'),' APISERVER_PUBLIC_KEY=',parameters('apiserverCertificate'),' SUBSCRIPTION_ID=',variables('subscriptionId'),' RESOURCE_GROUP=',variables('resourceGroup'),' LOCATION=',variables('location'),' VM_TYPE=',variables('vmType'),' SUBNET=',variables('subnetName'),' NETWORK_SECURITY_GROUP=',variables('nsgName'),' VIRTUAL_NETWORK=',variables('virtualNetworkName'),' VIRTUAL_NETWORK_RESOURCE_GROUP=',variables('virtualNetworkResourceGroupName'),' ROUTE_TABLE=',variables('routeTableName'),' PRIMARY_AVAILABILITY_SET=',variables('primaryAvailabilitySetName'),' PRIMARY_SCALE_SET=',variables('primaryScaleSetName'),' SERVICE_PRINCIPAL_CLIENT_ID=',variables('servicePrincipalClientId'),' SERVICE_PRINCIPAL_CLIENT_SECRET=',variables('singleQuote'),variables('servicePrincipalClientSecret'),variables('singleQuote'),' KUBELET_PRIVATE_KEY=',parameters('clientPrivateKey'),' TARGET_ENVIRONMENT=',parameters('targetEnvironment'),' NETWORK_PLUGIN=',parameters('networkPlugin'),' NETWORK_POLICY=',parameters('networkPolicy'),' VNET_CNI_PLUGINS_URL=',parameters('vnetCniLinuxPluginsURL'),' CNI_PLUGINS_URL=',parameters('cniPluginsURL'),' CLOUDPROVIDER_BACKOFF=',toLower(string(parameters('cloudproviderConfig').cloudProviderBackoff)),' CLOUDPROVIDER_BACKOFF_RETRIES=',parameters('cloudproviderConfig').cloudProviderBackoffRetries,' CLOUDPROVIDER_BACKOFF_EXPONENT=',parameters('cloudproviderConfig').cloudProviderBackoffExponent,' CLOUDPROVIDER_BACKOFF_DURATION=',parameters('cloudproviderConfig').cloudProviderBackoffDuration,' CLOUDPROVIDER_BACKOFF_JITTER=',parameters('cloudproviderConfig').cloudProviderBackoffJitter,' CLOUDPROVIDER_RATELIMIT=',toLower(string(parameters('cloudproviderConfig').cloudProviderRatelimit)),' CLOUDPROVIDER_RATELIMIT_QPS=',parameters('cloudproviderConfig').cloudProviderRatelimitQPS,' CLOUDPROVIDER_RATELIMIT_BUCKET=',parameters('cloudproviderConfig').cloudProviderRatelimitBucket,' USE_MANAGED_IDENTITY_EXTENSION=',variables('useManagedIdentityExtension'),' USER_ASSIGNED_IDENTITY_ID=',variables('userAssignedClientID'),' USE_INSTANCE_METADATA=',variables('useInstanceMetadata'),' LOAD_BALANCER_SKU=',variables('loadBalancerSku'),' EXCLUDE_MASTER_FROM_STANDARD_LB=',variables('excludeMasterFromStandardLB'),' MAXIMUM_LOADBALANCER_RULE_COUNT=',variables('maximumLoadBalancerRuleCount'),' CONTAINER_RUNTIME=',parameters('containerRuntime'),' CONTAINERD_DOWNLOAD_URL_BASE=',parameters('containerdDownloadURLBase'),' POD_INFRA_CONTAINER_SPEC=',parameters('kubernetesPodInfraContainerSpec'),' KMS_PROVIDER_VAULT_NAME=',variables('clusterKeyVaultName'),' IS_HOSTED_MASTER={{IsHostedMaster}}')]",
"provisionScriptParametersCommon": "[concat('ADMINUSER=',parameters('linuxAdminUsername'),' ETCD_DOWNLOAD_URL=',parameters('etcdDownloadURLBase'),' ETCD_VERSION=',parameters('etcdVersion'),' CONTAINERD_VERSION=',parameters('containerdVersion'),' MOBY_VERSION=',parameters('mobyVersion'),' DOCKER_ENGINE_REPO=',parameters('dockerEngineDownloadRepo'),' TENANT_ID=',variables('tenantID'),' KUBERNETES_VERSION={{.OrchestratorProfile.OrchestratorVersion}} HYPERKUBE_URL=',parameters('kubernetesHyperkubeSpec'),' APISERVER_PUBLIC_KEY=',parameters('apiserverCertificate'),' SUBSCRIPTION_ID=',variables('subscriptionId'),' RESOURCE_GROUP=',variables('resourceGroup'),' LOCATION=',variables('location'),' VM_TYPE=',variables('vmType'),' SUBNET=',variables('subnetName'),' NETWORK_SECURITY_GROUP=',variables('nsgName'),' VIRTUAL_NETWORK=',variables('virtualNetworkName'),' VIRTUAL_NETWORK_RESOURCE_GROUP=',variables('virtualNetworkResourceGroupName'),' ROUTE_TABLE=',variables('routeTableName'),' PRIMARY_AVAILABILITY_SET=',variables('primaryAvailabilitySetName'),' PRIMARY_SCALE_SET=',variables('primaryScaleSetName'),' SERVICE_PRINCIPAL_CLIENT_ID=',variables('servicePrincipalClientId'),' SERVICE_PRINCIPAL_CLIENT_SECRET=',variables('singleQuote'),variables('servicePrincipalClientSecret'),variables('singleQuote'),' KUBELET_PRIVATE_KEY=',parameters('clientPrivateKey'),' TARGET_ENVIRONMENT=',parameters('targetEnvironment'),' NETWORK_PLUGIN=',parameters('networkPlugin'),' NETWORK_POLICY=',parameters('networkPolicy'),' VNET_CNI_PLUGINS_URL=',parameters('vnetCniLinuxPluginsURL'),' CNI_PLUGINS_URL=',parameters('cniPluginsURL'),' CLOUDPROVIDER_BACKOFF=',toLower(string(parameters('cloudproviderConfig').cloudProviderBackoff)),' CLOUDPROVIDER_BACKOFF_RETRIES=',parameters('cloudproviderConfig').cloudProviderBackoffRetries,' CLOUDPROVIDER_BACKOFF_EXPONENT=',parameters('cloudproviderConfig').cloudProviderBackoffExponent,' CLOUDPROVIDER_BACKOFF_DURATION=',parameters('cloudproviderConfig').cloudProviderBackoffDuration,' CLOUDPROVIDER_BACKOFF_JITTER=',parameters('cloudproviderConfig').cloudProviderBackoffJitter,' CLOUDPROVIDER_RATELIMIT=',toLower(string(parameters('cloudproviderConfig').cloudProviderRatelimit)),' CLOUDPROVIDER_RATELIMIT_QPS=',parameters('cloudproviderConfig').cloudProviderRatelimitQPS,' CLOUDPROVIDER_RATELIMIT_BUCKET=',parameters('cloudproviderConfig').cloudProviderRatelimitBucket,' USE_MANAGED_IDENTITY_EXTENSION=',variables('useManagedIdentityExtension'),' USER_ASSIGNED_IDENTITY_ID=',variables('userAssignedClientID'),' USE_INSTANCE_METADATA=',variables('useInstanceMetadata'),' LOAD_BALANCER_SKU=',variables('loadBalancerSku'),' EXCLUDE_MASTER_FROM_STANDARD_LB=',variables('excludeMasterFromStandardLB'),' MAXIMUM_LOADBALANCER_RULE_COUNT=',variables('maximumLoadBalancerRuleCount'),' CONTAINER_RUNTIME=',parameters('containerRuntime'),' CONTAINERD_DOWNLOAD_URL_BASE=',parameters('containerdDownloadURLBase'),' POD_INFRA_CONTAINER_SPEC=',parameters('kubernetesPodInfraContainerSpec'),' KMS_PROVIDER_VAULT_NAME=',variables('clusterKeyVaultName'),' IS_HOSTED_MASTER={{IsHostedMaster}} PRIVATE_AZURE_REGISTRY_SERVER=',parameters('privateAzureRegistryServer'))]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have been separated by a comma. I'll fix this in another PR.

{{if not IsHostedMaster}}
{{if IsMasterVirtualMachineScaleSets}}
"provisionScriptParametersMaster": "[concat('COSMOS_URI={{ GetCosmosEndPointUri }} MASTER_NODE=true NO_OUTBOUND={{IsFeatureEnabled "BlockOutboundInternet"}} CLUSTER_AUTOSCALER_ADDON=',parameters('kubernetesClusterAutoscalerEnabled'),' ACI_CONNECTOR_ADDON=',parameters('kubernetesACIConnectorEnabled'),' APISERVER_PRIVATE_KEY=',parameters('apiServerPrivateKey'),' CA_CERTIFICATE=',parameters('caCertificate'),' CA_PRIVATE_KEY=',parameters('caPrivateKey'),' MASTER_FQDN=',variables('masterFqdnPrefix'),' KUBECONFIG_CERTIFICATE=',parameters('kubeConfigCertificate'),' KUBECONFIG_KEY=',parameters('kubeConfigPrivateKey'),' ETCD_SERVER_CERTIFICATE=',parameters('etcdServerCertificate'),' ETCD_CLIENT_CERTIFICATE=',parameters('etcdClientCertificate'),' ETCD_SERVER_PRIVATE_KEY=',parameters('etcdServerPrivateKey'),' ETCD_CLIENT_PRIVATE_KEY=',parameters('etcdClientPrivateKey'),' ETCD_PEER_CERTIFICATES=',string(variables('etcdPeerCertificates')),' ETCD_PEER_PRIVATE_KEYS=',string(variables('etcdPeerPrivateKeys')),' ENABLE_AGGREGATED_APIS=',string(parameters('enableAggregatedAPIs')),' KUBECONFIG_SERVER=',variables('kubeconfigServer'))]",
Expand Down
6 changes: 6 additions & 0 deletions parts/k8s/kubernetesparams.t
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@
},
"type": "string"
},
"privateAzureRegistryServer": {
"metadata": {
"description": "The private Azure registry server for hyperkube."
},
"type": "string"
},
"kubernetesCcmImageSpec": {
"defaultValue": "",
"metadata": {
Expand Down
1 change: 1 addition & 0 deletions pkg/api/converterfromapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ func convertKubernetesConfigToVLabs(apiCfg *KubernetesConfig, vlabsCfg *vlabs.Ku
vlabsCfg.KeyVaultSku = apiCfg.KeyVaultSku
vlabsCfg.MaximumLoadBalancerRuleCount = apiCfg.MaximumLoadBalancerRuleCount
vlabsCfg.ProxyMode = vlabs.KubeProxyMode(apiCfg.ProxyMode)
vlabsCfg.PrivateAzureRegistryServer = apiCfg.PrivateAzureRegistryServer
convertAddonsToVlabs(apiCfg, vlabsCfg)
convertKubeletConfigToVlabs(apiCfg, vlabsCfg)
convertControllerManagerConfigToVlabs(apiCfg, vlabsCfg)
Expand Down
1 change: 1 addition & 0 deletions pkg/api/convertertoapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ func convertVLabsKubernetesConfig(vlabs *vlabs.KubernetesConfig, api *Kubernetes
api.KeyVaultSku = vlabs.KeyVaultSku
api.MaximumLoadBalancerRuleCount = vlabs.MaximumLoadBalancerRuleCount
api.ProxyMode = KubeProxyMode(vlabs.ProxyMode)
api.PrivateAzureRegistryServer = vlabs.PrivateAzureRegistryServer
convertAddonsToAPI(vlabs, api)
convertKubeletConfigToAPI(vlabs, api)
convertControllerManagerConfigToAPI(vlabs, api)
Expand Down
1 change: 1 addition & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ type KubernetesConfig struct {
KeyVaultSku string `json:"keyVaultSku,omitempty"`
MaximumLoadBalancerRuleCount int `json:"maximumLoadBalancerRuleCount,omitempty"`
ProxyMode KubeProxyMode `json:"kubeProxyMode,omitempty"`
PrivateAzureRegistryServer string `json:"privateAzureRegistryServer,omitempty"`
}

// CustomFile has source as the full absolute source path to a file and dest
Expand Down
37 changes: 37 additions & 0 deletions pkg/api/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
)

const exampleCustomHyperkubeImage = `example.azurecr.io/example/hyperkube-amd64:custom`
const examplePrivateAzureRegistryServer = `example.azurecr.io`

const exampleAPIModel = `{
"apiVersion": "vlabs",
Expand All @@ -37,6 +38,26 @@ const exampleAPIModel = `{
}
`

const exampleAPIModelWithPrivateAzureRegistry = `{
"apiVersion": "vlabs",
"properties": {
"orchestratorProfile": {
"orchestratorType": "Kubernetes",
"kubernetesConfig": {
"customHyperkubeImage": "` + exampleCustomHyperkubeImage + `",
"privateAzureRegistryServer": "` + examplePrivateAzureRegistryServer + `"
}
},
"masterProfile": { "count": 1, "dnsPrefix": "", "vmSize": "Standard_D2_v2" },
"agentPoolProfiles": [ { "name": "linuxpool1", "count": 2, "vmSize": "Standard_D2_v2", "availabilityProfile": "AvailabilitySet" } ],
"windowsProfile": { "adminUsername": "azureuser", "adminPassword": "replacepassword1234$" },
"linuxProfile": { "adminUsername": "azureuser", "ssh": { "publicKeys": [ { "keyData": "" } ] }
},
"servicePrincipalProfile": { "clientId": "", "secret": "" }
}
}
`

const exampleSystemMSIModel = `{
"apiVersion": "vlabs",
"properties": {
Expand Down Expand Up @@ -958,6 +979,22 @@ func TestCustomHyperkubeImageField(t *testing.T) {
}
}

func TestPrivateAzureRegistryServerField(t *testing.T) {
log.Println(exampleAPIModelWithPrivateAzureRegistry)
apiloader := &Apiloader{
Translator: nil,
}
apimodel, _, err := apiloader.DeserializeContainerService([]byte(exampleAPIModelWithPrivateAzureRegistry), false, false, nil)
if err != nil {
t.Fatalf("unexpectedly error deserializing the example apimodel: %s", err)
}

actualPrivateAzureRegistryServer := apimodel.Properties.OrchestratorProfile.KubernetesConfig.PrivateAzureRegistryServer
if actualPrivateAzureRegistryServer != examplePrivateAzureRegistryServer {
t.Fatalf("kubernetesConfig->privateAzureRegistryServer field value was unexpected: got(%s), expected(%s)", actualPrivateAzureRegistryServer, examplePrivateAzureRegistryServer)
}
}

func TestUserAssignedMSI(t *testing.T) {
// Test1: With just System MSI
log.Println(exampleSystemMSIModel)
Expand Down
1 change: 1 addition & 0 deletions pkg/api/vlabs/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ type KubernetesConfig struct {
KeyVaultSku string `json:"keyVaultSku,omitempty"`
MaximumLoadBalancerRuleCount int `json:"maximumLoadBalancerRuleCount,omitempty"`
ProxyMode KubeProxyMode `json:"kubeProxyMode,omitempty"`
PrivateAzureRegistryServer string `json:"privateAzureRegistryServer,omitempty"`
}

// CustomFile has source as the full absolute source path to a file and dest
Expand Down
22 changes: 22 additions & 0 deletions pkg/api/vlabs/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,28 @@ func (k *KubernetesConfig) Validate(k8sVersion string, hasWindows bool) error {
if e := k.validateNetworkPluginPlusPolicy(); e != nil {
return e
}
if e := k.validatePrivateAzureRegistryServer(); e != nil {
return e
}

return nil
}

func (k *KubernetesConfig) validatePrivateAzureRegistryServer() error {

// Check PrivateAzureRegistryServer has a valid value.
valid := false
if k.PrivateAzureRegistryServer != "" {
if k.CustomHyperkubeImage != "" {
valid = true
}
} else {
valid = true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this logic hard to read. would something like the following be more more readable? I think that would remove the need for the valid check

k.PrivateAzureRegistryServer != "" and k.CustomHyperkubeImage == "" {
    return errors.Errorf("customHyperkubeImage must be provided when privateAzureRegistryServer is provided")
}


if !valid {
return errors.Errorf("customHyperkubeImage must be provided when privateAzureRegistryServer is provided")
}

return nil
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/api/vlabs/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,25 @@ func Test_KubernetesConfig_Validate(t *testing.T) {
}
}

func Test_Properties_ValidatePrivateAzureRegistryServer(t *testing.T) {
p := &Properties{}
p.OrchestratorProfile = &OrchestratorProfile{}
p.OrchestratorProfile.OrchestratorType = Kubernetes
p.OrchestratorProfile.KubernetesConfig = &KubernetesConfig{}

p.OrchestratorProfile.KubernetesConfig.PrivateAzureRegistryServer = "example.azurecr.io"
err := p.OrchestratorProfile.KubernetesConfig.validatePrivateAzureRegistryServer()
expectedMsg := "customHyperkubeImage must be provided when privateAzureRegistryServer is provided"
if err.Error() != expectedMsg {
t.Errorf("expected error message : %s to be thrown, but got : %s", expectedMsg, err.Error())
}
p.OrchestratorProfile.KubernetesConfig.CustomHyperkubeImage = "example.azurecr.io/hyperkube-amd64:tag"
err = p.OrchestratorProfile.KubernetesConfig.validatePrivateAzureRegistryServer()
if err != nil {
t.Errorf("should not error because CustomHyperkubeImage is provided, got error : %s", err.Error())
}
}

func Test_Properties_ValidateNetworkPolicy(t *testing.T) {
p := &Properties{}
p.OrchestratorProfile = &OrchestratorProfile{}
Expand Down
3 changes: 3 additions & 0 deletions pkg/engine/params_k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func assignKubernetesParameters(properties *api.Properties, parametersMap params

addValue(parametersMap, "kubeDNSServiceIP", kubernetesConfig.DNSServiceIP)
addValue(parametersMap, "kubernetesHyperkubeSpec", kubernetesHyperkubeSpec)
if kubernetesConfig.PrivateAzureRegistryServer != "" {
addValue(parametersMap, "privateAzureRegistryServer", kubernetesConfig.PrivateAzureRegistryServer)
}
addValue(parametersMap, "kubernetesAddonManagerSpec", kubernetesImageBase+k8sComponents["addonmanager"])
if orchestratorProfile.NeedsExecHealthz() {
addValue(parametersMap, "kubernetesExecHealthzSpec", kubernetesImageBase+k8sComponents["exechealthz"])
Expand Down