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

Commit

Permalink
feat: add support for private registry (#523)
Browse files Browse the repository at this point in the history
  • Loading branch information
ritazh authored and jackfrancis committed Feb 16, 2019
1 parent 1d97996 commit 7e175c8
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 4 deletions.
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
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'))]",
{{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
}

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

0 comments on commit 7e175c8

Please sign in to comment.