AKS features (#12)
- Significant enhancements for AKS: - Fully support creating clusters with managed identities. This is recommended and the new default, compared to the older method of using service principals to control cluster resources. - Support creating clusters using VM scalesets for the cluster nodes. This is recommended and the new default, compared to using individual VMs. - Support private clusters. - Support node autoscaling for agent pools backed by VM scalesets. - Support spot (low-priority) nodes for agent pools backed by VM scalesets. - New methods for the `az_kubernetes_service` class, for managing agent pools: `get_agent_pool`, `create_agent_pool`, `delete_agent_pool` and `list_agent_pools`. Creating new agent pools requires VM scalesets, as mentioned above. - New `agent_pool` function to supply the parameters for a _single_ AKS agent pool. - The functions to call external tools (`call_docker`, `call_docker_compose`, `call_kubernetes` and `call_helm`) now use the value of the system option `azure_containers_tool_echo` to determine whether to echo output to the screen. If this is unset, the fallback is `TRUE` (as in previous versions). Closes #9, closes #10, closes #11
This commit is contained in:
Родитель
64df96cea0
Коммит
15b0106943
|
@ -1,6 +1,6 @@
|
|||
Package: AzureContainers
|
||||
Title: Interface to 'Container Instances', 'Docker Registry' and 'Kubernetes' in 'Azure'
|
||||
Version: 1.2.1
|
||||
Version: 1.2.1.9000
|
||||
Authors@R: c(
|
||||
person("Hong", "Ooi", , "hongooi@microsoft.com", role = c("aut", "cre")),
|
||||
person("Bill", "Liang", role = "ctb", comment = "Assistance debugging MMLS on Kubernetes"),
|
||||
|
@ -26,5 +26,5 @@ Suggests:
|
|||
knitr,
|
||||
testthat,
|
||||
uuid
|
||||
Roxygen: list(markdown=TRUE)
|
||||
RoxygenNote: 6.1.1
|
||||
Roxygen: list(markdown=TRUE, r6=FALSE, old_usage=TRUE)
|
||||
RoxygenNote: 7.1.0
|
||||
|
|
|
@ -6,6 +6,7 @@ export(aci)
|
|||
export(aci_creds)
|
||||
export(aci_ports)
|
||||
export(acr)
|
||||
export(agent_pool)
|
||||
export(aks)
|
||||
export(aks_pools)
|
||||
export(call_docker)
|
||||
|
|
12
NEWS.md
12
NEWS.md
|
@ -1,3 +1,15 @@
|
|||
# AzureContainers 1.2.1.9000
|
||||
|
||||
- Significant enhancements for AKS:
|
||||
- Fully support creating clusters with managed identities. This is recommended and the new default, compared to the older method of using service principals to control cluster resources.
|
||||
- Support creating clusters using VM scalesets for the cluster nodes. This is recommended and the new default, compared to using individual VMs.
|
||||
- Support private clusters.
|
||||
- Support node autoscaling for agent pools backed by VM scalesets.
|
||||
- Support spot (low-priority) nodes for agent pools backed by VM scalesets.
|
||||
- New methods for the `az_kubernetes_service` class, for managing agent pools: `get_agent_pool`, `create_agent_pool`, `delete_agent_pool` and `list_agent_pools`. Creating new agent pools requires VM scalesets, as mentioned above.
|
||||
- New `agent_pool` function to supply the parameters for a _single_ AKS agent pool.
|
||||
- The functions to call external tools (`call_docker`, `call_docker_compose`, `call_kubernetes` and `call_helm`) now use the value of the system option `azure_containers_tool_echo` to determine whether to echo output to the screen. If this is unset, the fallback is `TRUE` (as in previous versions).
|
||||
|
||||
# AzureContainers 1.2.1
|
||||
|
||||
- Fix a bug where `call_docker_compose` could be checking for the wrong binary.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#' @section Arguments:
|
||||
#' - `name`: The name of the container registry.
|
||||
#' - `location`: The location/region in which to create the container registry. Defaults to this resource group's location.
|
||||
#' - `admin_user_enabled`: Whether to enable the Admin user. Currently this must be TRUE to allow Docker to access the registry.
|
||||
#' - `admin_user_enabled`: Whether to enable the Admin user. Currently this must be `TRUE` for ACI to pull from the registry.
|
||||
#' - `sku`: Either "Basic", "Standard" (the default) or "Premium".
|
||||
#' - `wait`: Whether to wait until the ACR resource provisioning is complete.
|
||||
#' - `...`: Other named arguments to pass to the [az_resource] initialization function.
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
#' ```
|
||||
#' create_aks(name, location = self$location,
|
||||
#' dns_prefix = name, kubernetes_version = NULL,
|
||||
#' enable_rbac = FALSE, agent_pools = list(),
|
||||
#' enable_rbac = FALSE, agent_pools = agent_pool("pool1", 3),
|
||||
#' login_user = "", login_passkey = "",
|
||||
#' cluster_service_principal = NULL, managed_identity = FALSE,
|
||||
#' cluster_service_principal = NULL, managed_identity = TRUE,
|
||||
#' private_cluster = FALSE,
|
||||
#' properties = list(), ..., wait = TRUE)
|
||||
#' ```
|
||||
#' @section Arguments:
|
||||
|
@ -19,11 +20,12 @@
|
|||
#' - `location`: The location/region in which to create the service. Defaults to this resource group's location.
|
||||
#' - `dns_prefix`: The domain name prefix to use for the cluster endpoint. The actual domain name will start with this argument, followed by a string of pseudorandom characters.
|
||||
#' - `kubernetes_version`: The Kubernetes version to use. If not specified, uses the most recent version of Kubernetes available.
|
||||
#' - `enable_rbac`: Whether to enable role-based access controls.
|
||||
#' - `agent_pools`: A list of pool specifications. See 'Details'.
|
||||
#' - `enable_rbac`: Whether to enable Kubernetes role-based access controls (which is distinct from Azure AD RBAC).
|
||||
#' - `agent_pools`: The pool specification(s) for the cluster. See 'Details'.
|
||||
#' - `login_user,login_passkey`: Optionally, a login username and public key (on Linux). Specify these if you want to be able to ssh into the cluster nodes.
|
||||
#' - `cluster_service_principal`: The service principal (client) that AKS will use to manage the cluster resources. This should be a list, with the first component being the client ID and the second the client secret. If not supplied, a new service principal will be created (requires an interactive session).
|
||||
#' - `managed_identity`: Whether the cluster should have a managed identity assigned to it. This is currently in preview; see the [Microsoft Docs page](https://docs.microsoft.com/en-us/azure/aks/use-managed-identity) for enabling this feature.
|
||||
#' - `cluster_service_principal`: The service principal that AKS will use to manage the cluster resources. This should be a list, with the first component being the client ID and the second the client secret. If not supplied, a new service principal will be created (requires an interactive session). Ignored if `managed_identity=TRUE`, which is the default.
|
||||
#' - `managed_identity`: Whether the cluster should have a managed identity assigned to it. If `FALSE`, a service principal will be used to manage the cluster's resources; see 'Details' below.
|
||||
#' - `private_cluster`: Whether this cluster is private (not visible from the public Internet). A private cluster is accessible only to hosts on its virtual network.
|
||||
#' - `properties`: A named list of further Kubernetes-specific properties to pass to the initialization function.
|
||||
#' - `wait`: Whether to wait until the AKS resource provisioning is complete. Note that provisioning a Kubernetes cluster can take several minutes.
|
||||
#' - `...`: Other named arguments to pass to the initialization function.
|
||||
|
@ -31,15 +33,19 @@
|
|||
#' @section Details:
|
||||
#' An AKS resource is a Kubernetes cluster hosted in Azure. See the [documentation for the resource][aks] for more information. To work with the cluster (deploy images, define and start services, etc) see the [documentation for the cluster endpoint][kubernetes_cluster].
|
||||
#'
|
||||
#' To specify the agent pools for the cluster, it is easiest to use the [aks_pools] function. This takes as arguments the name(s) of the pools, the number of nodes, the VM size(s) to use, and the operating system (Windows or Linux) to run on the VMs.
|
||||
#' The nodes for an AKS cluster are organised into _agent pools_, also known as _node pools_, which are homogenous groups of virtual machines. To specify the details for a single agent pool, use the `agent_pool` function, which returns an S3 object of that class. To specify the details for multiple pools, you can supply a list of such objects, or a single call to the `aks_pools` function; see the examples below. Note that `aks_pools` is older, and does not support all the possible parameters for an agent pool.
|
||||
#'
|
||||
#' By default, the password for a newly-created service principal will expire after one year. You can run the `update_service_password` method of the AKS object to reset/update the password before it expires.
|
||||
#' Of the agent pools in a cluster, at least one must be a _system pool_, which is used to host critical system pods such as CoreDNS and tunnelfront. If you specify more than one pool, the first pool will be treated as the system pool. Note that there are certain [extra requirements](https://docs.microsoft.com/en-us/azure/aks/use-system-pools) for the system pool.
|
||||
#'
|
||||
#' An AKS cluster requires an identity to manage the low-level resources it uses, such as virtual machines and networks. The default and recommended method is to use a _managed identity_, in which all the details of this process are handled by AKS. In AzureContainers version 1.2.1 and older, a _service principal_ was used instead, which is an older and less automated method. By setting `managed_identity=FALSE`, you can continue using a service principal instead of a managed identity.
|
||||
#'
|
||||
#' One thing to be aware of with service principals is that they have a secret password that will expire eventually. By default, the password for a newly-created service principal will expire after one year. You should run the `update_service_password` method of the AKS object to reset/update the password before it expires.
|
||||
#'
|
||||
#' @section Value:
|
||||
#' An object of class `az_kubernetes_service` representing the service.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [get_aks], [delete_aks], [list_aks], [aks_pools]
|
||||
#' [get_aks], [delete_aks], [list_aks], [agent_pool], [aks_pools]
|
||||
#'
|
||||
#' [az_kubernetes_service]
|
||||
#'
|
||||
|
@ -57,10 +63,20 @@
|
|||
#' get_subscription("subscription_id")$
|
||||
#' get_resource_group("rgname")
|
||||
#'
|
||||
#' rg$create_aks("mycluster", agent_pools=aks_pools("pool1", 5))
|
||||
#' rg$create_aks("mycluster", agent_pools=agent_pool("pool1", 5))
|
||||
#'
|
||||
#' # GPU-enabled cluster
|
||||
#' rg$create_aks("mygpucluster", agent_pools=aks_pools("pool1", 5, size="Standard_NC6s_v3"))
|
||||
#' rg$create_aks("mygpucluster", agent_pools=agent_pool("pool1", 5, size="Standard_NC6s_v3"))
|
||||
#'
|
||||
#' # multiple agent pools
|
||||
#' rg$create_aks("mycluster", agent_pools=list(
|
||||
#' agent_pool("pool1", 2),
|
||||
#' agent_pool("pool2", 3, size="Standard_NC6s_v3")
|
||||
#' ))
|
||||
#'
|
||||
#' # deprecated alternative for multiple pools
|
||||
#' rg$create_aks("mycluster",
|
||||
#' agent_pools=aks_pools(c("pool1", "pool2"), c(2, 3), c("Standard_DS2_v2", "Standard_NC6s_v3")))
|
||||
#'
|
||||
#' }
|
||||
NULL
|
||||
|
@ -205,35 +221,59 @@ add_aks_methods <- function()
|
|||
function(name, location=self$location,
|
||||
dns_prefix=name, kubernetes_version=NULL,
|
||||
login_user="", login_passkey="",
|
||||
enable_rbac=FALSE, agent_pools=list(),
|
||||
enable_rbac=FALSE, agent_pools=agent_pool("pool1", 3),
|
||||
cluster_service_principal=NULL,
|
||||
managed_identity=FALSE,
|
||||
managed_identity=TRUE, private_cluster=FALSE,
|
||||
properties=list(), ..., wait=TRUE)
|
||||
{
|
||||
if(is_empty(kubernetes_version))
|
||||
kubernetes_version <- tail(self$list_kubernetes_versions(), 1)
|
||||
|
||||
# hide from CRAN check
|
||||
find_app_creds <- get("find_app_creds", getNamespace("AzureContainers"))
|
||||
cluster_service_principal <- find_app_creds(cluster_service_principal, name, location, self$token)
|
||||
# figure out how to handle managing resources: either identity, or SP
|
||||
if(managed_identity)
|
||||
{
|
||||
identity <- list(type="systemAssigned")
|
||||
sp_profile <- NULL
|
||||
}
|
||||
else
|
||||
{
|
||||
identity <- NULL
|
||||
# hide from CRAN check
|
||||
find_app_creds <- get("find_app_creds", getNamespace("AzureContainers"))
|
||||
cluster_service_principal <- find_app_creds(cluster_service_principal, name, location, self$token)
|
||||
|
||||
if(is.null(cluster_service_principal[[2]]))
|
||||
stop("Must provide a service principal with a secret password", call.=FALSE)
|
||||
|
||||
sp_profile <- list(
|
||||
clientId=cluster_service_principal[[1]],
|
||||
secret=cluster_service_principal[[2]]
|
||||
)
|
||||
}
|
||||
|
||||
if(inherits(agent_pools, "agent_pool"))
|
||||
agent_pools <- list(unclass(agent_pools))
|
||||
else if(is.list(agent_pools) && all(sapply(agent_pools, inherits, "agent_pool")))
|
||||
agent_pools <- lapply(agent_pools, unclass)
|
||||
|
||||
# 1st agent pool is system
|
||||
agent_pools[[1]]$mode <- "System"
|
||||
|
||||
props <- list(
|
||||
kubernetesVersion=kubernetes_version,
|
||||
dnsPrefix=dns_prefix,
|
||||
agentPoolProfiles=agent_pools,
|
||||
enableRBAC=enable_rbac,
|
||||
servicePrincipalProfile=list(
|
||||
clientId=cluster_service_principal[[1]],
|
||||
secret=cluster_service_principal[[2]]
|
||||
)
|
||||
enableRBAC=enable_rbac
|
||||
)
|
||||
|
||||
if(is.null(props$servicePrincipalProfile$secret))
|
||||
stop("Must provide a service principal with a secret password", call.=FALSE)
|
||||
if(private_cluster)
|
||||
{
|
||||
props$apiServerAccessProfile <- list(enablePrivateCluster=private_cluster)
|
||||
props$networkProfile <- list(loadBalancerSku="standard")
|
||||
}
|
||||
|
||||
identity <- if(managed_identity)
|
||||
list(type="systemAssigned")
|
||||
else NULL
|
||||
if(!is.null(sp_profile))
|
||||
props$servicePrincipalProfile <- sp_profile
|
||||
|
||||
if(login_user != "" && login_passkey != "")
|
||||
props$linuxProfile <- list(
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# stub class, may be expanded later
|
||||
az_agent_pool <- R6::R6Class("az_agent_pool", inherit=AzureRMR::az_resource)
|
|
@ -68,9 +68,18 @@ public=list(
|
|||
{
|
||||
if(is_aks(principal))
|
||||
{
|
||||
tenant <- self$token$tenant
|
||||
gr <- graph_login(tenant, ...)
|
||||
principal <- gr$get_app(principal$properties$servicePrincipalProfile$clientId)
|
||||
clientid <- principal$properties$servicePrincipalProfile$clientId
|
||||
if(clientid == "msi")
|
||||
{
|
||||
ident <- principal$properties$identityProfile
|
||||
principal <- ident[[1]]$objectId
|
||||
}
|
||||
else
|
||||
{
|
||||
tenant <- self$token$tenant
|
||||
principal <- graph_login(tenant, ...)$get_app(clientid)
|
||||
|
||||
}
|
||||
}
|
||||
super$add_role_assignment(principal, role, scope)
|
||||
},
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
#' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class:
|
||||
#' - `new(...)`: Initialize a new AKS object.
|
||||
#' - `get_cluster(config, role)`: Return an object representing the Docker registry endpoint.
|
||||
#' - `get_agent_pool(pollname)`: Returns an object of class `az_agent_pool` representing an agent pool. This class inherits from [AzureRMR::az_resource]; it currently has no extra methods, but these may be added in the future.
|
||||
#' - `list_agent_pools()`: Returns a list of agent pool objects.
|
||||
#' - `create_agent_pool(poolname, ..., wait=FALSE)`: Creates a new agent pool. See the [agent_pool] function for further arguments to this method.
|
||||
#' - `delete_agent_pool(poolname, confirm=TRUE. wait=FALSE)`: Deletes an agent pool.
|
||||
#' - `list_cluster_resources()`: Returns a list of all the Azure resources managed by the cluster.
|
||||
#' - `update_aad_password(name=NULL, duration=NULL, ...)`: Update the password for Azure Active Directory integration, returning the new password invisibly. See 'Updating credentials' below.
|
||||
#' - `update_service_password(name=NULL, duration=NULL, ...)`: Update the password for the service principal used to manage the cluster resources, returning the new password invisibly. See 'Updating credentials' below.
|
||||
|
@ -22,9 +26,9 @@
|
|||
#' - `role`: This can be `"User"` (the default) or `"Admin"`.
|
||||
#'
|
||||
#' @section Updating credentials:
|
||||
#' An AKS cluster requires at least one, and possibly three, service principals. The first service principal is used to manage the resources used by the cluster: the VMs, networking resources, virtual disks, etc. The other two are used for AAD integration. These service principals have secret passwords, which have to be refreshed as they expire.
|
||||
#' An AKS resource can have up to three service principals associated with it. Two of these are for Azure Active Directory (AAD) integration. The third is used to manage the subsidiary resources (VMs, networks, disks, etc) used by the cluster, if it doesn't have a service identity.
|
||||
#'
|
||||
#' The `update_aad_password()` and `update_service_password()` methods let you refresh the passwords for the cluster's service principals. Their arguments are:
|
||||
#' Any service principals used by the AKS resource will have secret passwords, which have to be refreshed as they expire. The `update_aad_password()` and `update_service_password()` methods let you refresh the passwords for the cluster's service principals. Their arguments are:
|
||||
#'
|
||||
#' - `name`: An optional friendly name for the password.
|
||||
#' - `duration`: The duration for which the new password is valid. Defaults to 2 years.
|
||||
|
@ -53,7 +57,14 @@
|
|||
#' # get the cluster endpoint
|
||||
#' kubclus <- myaks$get_cluster()
|
||||
#'
|
||||
#' # refresh the service principal password
|
||||
#' # list of agent pools
|
||||
#' myaks$list_agent_pools()
|
||||
#'
|
||||
#' # create a new agent pool, then delete it
|
||||
#' pool <- myaks$create_agent_pool("pool2", 3, size="Standard_DS3_v2")
|
||||
#' pool$delete()
|
||||
#'
|
||||
#' # refresh the service principal password (mostly for legacy clusters without a managed identity)
|
||||
#' myaks$update_service_password()
|
||||
#'
|
||||
#' # refresh the service principal password, using custom credentials to authenticate with MS Graph
|
||||
|
@ -104,6 +115,43 @@ public=list(
|
|||
kubernetes_cluster(config=config)
|
||||
},
|
||||
|
||||
get_agent_pool=function(poolname)
|
||||
{
|
||||
az_agent_pool$new(self$token, self$subscription, resource_group=self$resource_group,
|
||||
type="Microsoft.ContainerService/managedClusters",
|
||||
name=file.path(self$name, "agentPools", poolname),
|
||||
api_version=self$get_api_version())
|
||||
},
|
||||
|
||||
create_agent_pool=function(poolname, ..., wait=FALSE)
|
||||
{
|
||||
pool <- agent_pool(name=poolname, ...)
|
||||
az_agent_pool$new(self$token, self$subscription, resource_group=self$resource_group,
|
||||
type="Microsoft.ContainerService/managedClusters",
|
||||
name=file.path(self$name, "agentPools", poolname),
|
||||
properties=pool[-1],
|
||||
api_version=self$get_api_version(),
|
||||
wait=wait)
|
||||
},
|
||||
|
||||
delete_agent_pool=function(poolname, confirm=TRUE, wait=FALSE)
|
||||
{
|
||||
az_agent_pool$new(self$token, self$subscription, resource_group=self$resource_group,
|
||||
type="Microsoft.ContainerService/managedClusters",
|
||||
name=file.path(self$name, "agentPools", poolname),
|
||||
deployed_properties=list(NULL),
|
||||
api_version=self$get_api_version())$
|
||||
delete(confirm=confirm, wait=wait)
|
||||
},
|
||||
|
||||
list_agent_pools=function()
|
||||
{
|
||||
cont <- self$do_operation("agentPools")
|
||||
api_version <- self$get_api_version()
|
||||
lapply(get_paged_list(cont, self$token), function(parms)
|
||||
az_agent_pool$new(self$token, self$subscription, deployed_properties=parms, api_version=api_version))
|
||||
},
|
||||
|
||||
list_cluster_resources=function()
|
||||
{
|
||||
clusrgname <- self$properties$nodeResourceGroup
|
||||
|
@ -129,6 +177,9 @@ public=list(
|
|||
update_service_password=function(name=NULL, duration=NULL, ...)
|
||||
{
|
||||
prof <- self$properties$servicePrincipalProfile
|
||||
if(prof$clientId == "msi")
|
||||
stop("Cluster uses service identity for managing resources", call.=FALSE)
|
||||
|
||||
app <- graph_login(self$token$tenant, ...)$get_app(prof$clientId)
|
||||
app$add_password(name, duration)
|
||||
prof$secret <- app$password
|
||||
|
@ -146,15 +197,86 @@ public=list(
|
|||
#' @param count The number of nodes per pool.
|
||||
#' @param size The VM type (size) to use for the pool. To see a list of available VM sizes, use the [list_vm_sizes] method for the resource group or subscription classes.
|
||||
#' @param os The operating system to use for the pool. Can be "Linux" or "Windows".
|
||||
#' @param disksize The OS disk size in gigabytes for each node in the pool. A value of 0 means to use the default disk size for the VM type.
|
||||
#' @param use_scaleset Whether to use a VM scaleset instead of individual VMs for this pool. A scaleset offers greater flexibility than individual VMs, and is the recommended method of creating an agent pool.
|
||||
#' @param low_priority If this pool uses a scaleset, whether it should be made up of spot (low-priority) VMs. A spot VM pool is cheaper, but is subject to being evicted to make room for other, higher-priority workloads. Ignored if `use_scaleset=FALSE`.
|
||||
#' @param cluster_autoscale The autoscaling parameters for the pool. This can be either `FALSE`, meaning autoscaling is disabled, or a vector of 2 numbers giving the minimum and maximum size of the agent pool. Ignored if `use_scaleset=FALSE`.
|
||||
#' @param ... Other named arguments, to be used as parameters for the agent pool.
|
||||
#'
|
||||
#' @details
|
||||
#' `agent_pool` is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster.
|
||||
#'
|
||||
#' @return
|
||||
#' An object of class `agent_pool`, suitable for passing to the `create_aks` constructor method.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [create_aks], [list_vm_sizes]
|
||||
#'
|
||||
#' [Agent pool parameters on Microsoft Docs](https://docs.microsoft.com/en-us/rest/api/aks/managedclusters/createorupdate#managedclusteragentpoolprofile)
|
||||
#'
|
||||
#' @examples
|
||||
#' # pool of 5 Linux GPU-enabled VMs
|
||||
#' agent_pool("pool1", 5, size="Standard_NC6s_v3")
|
||||
#'
|
||||
#' # pool of 3 Windows Server VMs, 500GB disk size each
|
||||
#' agent_pool("pool1", 3, os="Windows", disksize=500)
|
||||
#'
|
||||
#' # enable cluster autoscaling, with a minimum of 1 and maximum of 10 nodes
|
||||
#' agent_pool("pool1", 5, cluster_autoscale=c(1, 10))
|
||||
#'
|
||||
#' # use individual VMs rather than scaleset
|
||||
#' agent_pool("vmpool1", 3, use_scaleset=FALSE)
|
||||
#'
|
||||
#' @export
|
||||
agent_pool <- function(name, count, size="Standard_DS2_v2", os="Linux", disksize=0,
|
||||
use_scaleset=TRUE, low_priority=FALSE, cluster_autoscale=FALSE, ...)
|
||||
{
|
||||
parms <- list(
|
||||
name=name,
|
||||
count=as.integer(count),
|
||||
vmSize=size,
|
||||
osType=os,
|
||||
osDiskSizeGB=disksize,
|
||||
type=if(use_scaleset) "VirtualMachineScaleSets" else "AvailabilitySet"
|
||||
)
|
||||
|
||||
if(use_scaleset)
|
||||
{
|
||||
if(is.numeric(cluster_autoscale) && length(cluster_autoscale) == 2)
|
||||
{
|
||||
parms$enableAutoScaling <- TRUE
|
||||
parms$minCount <- min(cluster_autoscale)
|
||||
parms$maxCount <- max(cluster_autoscale)
|
||||
}
|
||||
if(low_priority)
|
||||
parms$scaleSetPriority <- "spot"
|
||||
}
|
||||
|
||||
extras <- list(...)
|
||||
if(!is_empty(extras))
|
||||
parms <- utils::modifyList(parms, extras)
|
||||
|
||||
structure(parms, class="agent_pool")
|
||||
}
|
||||
|
||||
|
||||
#' Vectorised utility function for specifying Kubernetes agent pools
|
||||
#'
|
||||
#' @param name The name(s) of the pool(s).
|
||||
#' @param count The number of nodes per pool.
|
||||
#' @param size The VM type (size) to use for the pool. To see a list of available VM sizes, use the [list_vm_sizes] method for the resource group or subscription classes.
|
||||
#' @param os The operating system to use for the pool. Can be "Linux" or "Windows".
|
||||
#'
|
||||
#' @details
|
||||
#' This is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster. You can specify multiple pools by providing vectors as input arguments; any scalar inputs will be replicated to match.
|
||||
#'
|
||||
#' `aks_pools` is deprecated; please use [agent_pool] going forward.
|
||||
#'
|
||||
#' @return
|
||||
#' A list of lists, suitable for passing to the `create_aks` constructor method.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [list_vm_sizes]
|
||||
#' [list_vm_sizes], [agent_pool]
|
||||
#'
|
||||
#' @examples
|
||||
#' # 1 pool of 5 Linux VMs
|
||||
|
@ -174,4 +296,3 @@ aks_pools <- function(name, count, size="Standard_DS2_v2", os="Linux")
|
|||
pool_df$name <- make.unique(pool_df$name, sep="")
|
||||
lapply(seq_len(nrow(pool_df)), function(i) unclass(pool_df[i, ]))
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
#'
|
||||
#' }
|
||||
#' @export
|
||||
call_docker <- function(cmd="", ..., echo=TRUE)
|
||||
call_docker <- function(cmd="", ..., echo=getOption("azure_containers_tool_echo", TRUE))
|
||||
{
|
||||
if(.AzureContainers$docker == "")
|
||||
stop("docker binary not found", call.=FALSE)
|
||||
|
@ -60,6 +60,7 @@ call_docker <- function(cmd="", ..., echo=TRUE)
|
|||
realcmd <- cmd
|
||||
}
|
||||
|
||||
echo <- as.logical(echo)
|
||||
val <- processx::run(dockercmd, strsplit(realcmd, " ", fixed=TRUE)[[1]], ..., echo=echo)
|
||||
val$cmdline <- paste("docker", cmd)
|
||||
invisible(val)
|
||||
|
@ -92,7 +93,7 @@ call_docker <- function(cmd="", ..., echo=TRUE)
|
|||
#'
|
||||
#' [Docker-compose command line reference](https://docs.docker.com/compose/)
|
||||
#' @export
|
||||
call_docker_compose <- function(cmd="", ..., echo=TRUE)
|
||||
call_docker_compose <- function(cmd="", ..., echo=getOption("azure_containers_tool_echo", TRUE))
|
||||
{
|
||||
if(.AzureContainers$dockercompose == "")
|
||||
stop("docker-compose binary not found", call.=FALSE)
|
||||
|
@ -110,6 +111,7 @@ call_docker_compose <- function(cmd="", ..., echo=TRUE)
|
|||
realcmd <- cmd
|
||||
}
|
||||
|
||||
echo <- as.logical(echo)
|
||||
val <- processx::run(dcmpcmd, strsplit(realcmd, " ", fixed=TRUE)[[1]], ..., echo=echo)
|
||||
val$cmdline <- paste("docker-compose", cmd)
|
||||
invisible(val)
|
||||
|
@ -161,7 +163,7 @@ call_docker_compose <- function(cmd="", ..., echo=TRUE)
|
|||
#'
|
||||
#' }
|
||||
#' @export
|
||||
call_kubectl <- function(cmd="", config=NULL, ..., echo=TRUE)
|
||||
call_kubectl <- function(cmd="", config=NULL, ..., echo=getOption("azure_containers_tool_echo", TRUE))
|
||||
{
|
||||
if(.AzureContainers$kubectl == "")
|
||||
stop("kubectl binary not found", call.=FALSE)
|
||||
|
@ -169,6 +171,8 @@ call_kubectl <- function(cmd="", config=NULL, ..., echo=TRUE)
|
|||
if(!is.null(config))
|
||||
config <- paste0("--kubeconfig=", config)
|
||||
message("Kubernetes operation: ", cmd, " ", config)
|
||||
|
||||
echo <- as.logical(echo)
|
||||
val <- processx::run(.AzureContainers$kubectl, c(strsplit(cmd, " ", fixed=TRUE)[[1]], config), ..., echo=echo)
|
||||
val$cmdline <- paste("kubectl", cmd, config)
|
||||
invisible(val)
|
||||
|
@ -203,7 +207,7 @@ call_kubectl <- function(cmd="", config=NULL, ..., echo=TRUE)
|
|||
#' [Kubectl command line reference](https://kubernetes.io/docs/reference/kubectl/overview/)
|
||||
#'
|
||||
#' @export
|
||||
call_helm <- function(cmd="", config=NULL, ..., echo=TRUE)
|
||||
call_helm <- function(cmd="", config=NULL, ..., echo=getOption("azure_containers_tool_echo", TRUE))
|
||||
{
|
||||
if(.AzureContainers$helm == "")
|
||||
stop("helm binary not found", call.=FALSE)
|
||||
|
@ -211,6 +215,8 @@ call_helm <- function(cmd="", config=NULL, ..., echo=TRUE)
|
|||
if(!is.null(config))
|
||||
config <- paste0("--kubeconfig=", config)
|
||||
message("Helm operation: ", cmd, " ", config)
|
||||
|
||||
echo <- as.logical(echo)
|
||||
val <- processx::run(.AzureContainers$helm, c(strsplit(cmd, " ", fixed=TRUE)[[1]], config), ..., echo=echo)
|
||||
val$cmdline <- paste("helm", cmd, config)
|
||||
invisible(val)
|
||||
|
|
|
@ -38,7 +38,7 @@ reg$push("newcontainer")
|
|||
# create Kubernetes cluster with 2 nodes
|
||||
aks <- resgroup$create_aks("myakscluster",
|
||||
location="australiaeast",
|
||||
agent_pools=aks_pools("pool1", 2, "Standard_DS2_v2", "Linux"))
|
||||
agent_pools=agent_pool("pool1", 2))
|
||||
|
||||
# give the cluster pull access to the registry
|
||||
acr$add_role_assignment(aks, "Acrpull")
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
\name{DockerRegistry}
|
||||
\alias{DockerRegistry}
|
||||
\title{Docker registry class}
|
||||
\format{An object of class \code{R6ClassGenerator} of length 24.}
|
||||
\usage{
|
||||
DockerRegistry
|
||||
}
|
||||
\description{
|
||||
Class representing a \href{https://docs.docker.com/registry/}{Docker registry}. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{docker_registry} function to instantiate new objects of this class.
|
||||
}
|
||||
|
@ -15,9 +11,9 @@ Class representing a \href{https://docs.docker.com/registry/}{Docker registry}.
|
|||
|
||||
The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class:
|
||||
\itemize{
|
||||
\item \code{login(...)}: Do a local login to the registry via \code{docker login}; necessary if you want to push and pull images. By default, instantiating a new object of this class will also log you in. See 'Details' below.
|
||||
\item \code{push(src_image, dest_image, ...)}: Push an image to the registry, using \code{docker tag} and \code{docker push}.
|
||||
\item \code{pull(image, ...)}: Pull an image from the registry, using \code{docker pull}.
|
||||
\item \code{login(...)}: Do a local login to the registry via \verb{docker login}; necessary if you want to push and pull images. By default, instantiating a new object of this class will also log you in. See 'Details' below.
|
||||
\item \code{push(src_image, dest_image, ...)}: Push an image to the registry, using \verb{docker tag} and \verb{docker push}.
|
||||
\item \code{pull(image, ...)}: Pull an image from the registry, using \verb{docker pull}.
|
||||
\item \code{get_image_manifest(image, tag="latest")}: Gets the manifest for an image.
|
||||
\item \code{get_image_digest(image, tag="latest")}: Gets the digest (SHA hash) for an image.
|
||||
\item \code{delete_image(image, digest, confirm=TRUE)}: Deletes an image from the registry.
|
||||
|
@ -65,4 +61,3 @@ reg$get_image_digest("myimage")
|
|||
|
||||
\href{https://docs.docker.com/registry/spec/api/}{Docker registry API}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
\name{KubernetesCluster}
|
||||
\alias{KubernetesCluster}
|
||||
\title{Kubernetes cluster class}
|
||||
\format{An object of class \code{R6ClassGenerator} of length 24.}
|
||||
\usage{
|
||||
KubernetesCluster
|
||||
}
|
||||
\description{
|
||||
Class representing a \href{https://kubernetes.io/docs/home/}{Kubernetes} cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{kubernetes_cluster} function to instantiate new objects of this class.
|
||||
}
|
||||
|
@ -18,12 +14,12 @@ The following methods are available, in addition to those provided by the \link[
|
|||
\item \code{new(...)}: Initialize a new registry object. See 'Initialization' below.
|
||||
\item \code{create_registry_secret(registry, secret_name, email)}: Provide authentication secret for a Docker registry. See 'Secrets' below.
|
||||
\item \code{delete_registry_secret(secret_name)}: Delete a registry authentication secret.
|
||||
\item \code{create(file, ...)}: Creates a deployment or service from a file, using \code{kubectl create -f}.
|
||||
\item \code{get(type, ...)}: Get information about resources, using \code{kubectl get}.
|
||||
\item \code{run(name, image, ...)}: Run an image using \code{kubectl run --image}.
|
||||
\item \code{expose(name, type, file, ...)}: Expose a service using \code{kubectl expose}. If the \code{file} argument is provided, read service information from there.
|
||||
\item \code{delete(type, name, file, ...)}: Delete a resource (deployment or service) using \code{kubectl delete}. If the \code{file} argument is provided, read resource information from there.
|
||||
\item \code{apply(file, ...)}: Apply a configuration file, using \code{kubectl apply -f}.
|
||||
\item \code{create(file, ...)}: Creates a deployment or service from a file, using \verb{kubectl create -f}.
|
||||
\item \code{get(type, ...)}: Get information about resources, using \verb{kubectl get}.
|
||||
\item \code{run(name, image, ...)}: Run an image using \verb{kubectl run --image}.
|
||||
\item \code{expose(name, type, file, ...)}: Expose a service using \verb{kubectl expose}. If the \code{file} argument is provided, read service information from there.
|
||||
\item \code{delete(type, name, file, ...)}: Delete a resource (deployment or service) using \verb{kubectl delete}. If the \code{file} argument is provided, read resource information from there.
|
||||
\item \code{apply(file, ...)}: Apply a configuration file, using \verb{kubectl apply -f}.
|
||||
\item \code{show_dashboard(port, ...)}: Display the cluster dashboard. By default, use local port 30000.
|
||||
\item \code{kubectl(cmd, ...)}: Run an arbitrary \code{kubectl} command on this cluster. Called by the other methods above.
|
||||
\item \code{helm(cmd, ...)}: Run a \code{helm} command on this cluster.
|
||||
|
@ -32,7 +28,7 @@ The following methods are available, in addition to those provided by the \link[
|
|||
|
||||
\section{Initialization}{
|
||||
|
||||
The \code{new()} method takes one argument: \code{config}, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \code{~/.kube/config} file.
|
||||
The \code{new()} method takes one argument: \code{config}, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \verb{~/.kube/config} file.
|
||||
}
|
||||
|
||||
\section{Secrets}{
|
||||
|
@ -122,4 +118,3 @@ kubclus$get("service")
|
|||
|
||||
\href{https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands}{Kubectl commandline reference}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
\alias{aci}
|
||||
\alias{az_container_instance}
|
||||
\title{Azure Container Instance class}
|
||||
\format{An object of class \code{R6ClassGenerator} of length 24.}
|
||||
\usage{
|
||||
aci
|
||||
}
|
||||
\description{
|
||||
Class representing an Azure Container Instance (ACI) resource.
|
||||
}
|
||||
|
@ -49,4 +45,3 @@ myaci$restart()
|
|||
|
||||
\href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
\alias{acr}
|
||||
\alias{az_container_registry}
|
||||
\title{Azure Container Registry class}
|
||||
\format{An object of class \code{R6ClassGenerator} of length 24.}
|
||||
\usage{
|
||||
acr
|
||||
}
|
||||
\description{
|
||||
Class representing an Azure Container Registry (ACR) resource. For working with the registry endpoint itself, including uploading and downloading images etc, see \link{docker_registry}.
|
||||
}
|
||||
|
@ -75,4 +71,3 @@ myacr$get_docker_registry(as_admin=TRUE)
|
|||
\href{https://docs.microsoft.com/en-us/azure/container-registry/}{Azure Container Registry} and
|
||||
\href{https://docs.microsoft.com/en-us/rest/api/containerregistry/registries}{API reference}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/az_kubernetes_service.R
|
||||
\name{agent_pool}
|
||||
\alias{agent_pool}
|
||||
\title{Utility function for specifying Kubernetes agent pools}
|
||||
\usage{
|
||||
agent_pool(name, count, size = "Standard_DS2_v2", os = "Linux",
|
||||
disksize = 0, use_scaleset = TRUE, low_priority = FALSE,
|
||||
cluster_autoscale = FALSE, ...)
|
||||
}
|
||||
\arguments{
|
||||
\item{name}{The name(s) of the pool(s).}
|
||||
|
||||
\item{count}{The number of nodes per pool.}
|
||||
|
||||
\item{size}{The VM type (size) to use for the pool. To see a list of available VM sizes, use the \link{list_vm_sizes} method for the resource group or subscription classes.}
|
||||
|
||||
\item{os}{The operating system to use for the pool. Can be "Linux" or "Windows".}
|
||||
|
||||
\item{disksize}{The OS disk size in gigabytes for each node in the pool. A value of 0 means to use the default disk size for the VM type.}
|
||||
|
||||
\item{use_scaleset}{Whether to use a VM scaleset instead of individual VMs for this pool. A scaleset offers greater flexibility than individual VMs, and is the recommended method of creating an agent pool.}
|
||||
|
||||
\item{low_priority}{If this pool uses a scaleset, whether it should be made up of spot (low-priority) VMs. A spot VM pool is cheaper, but is subject to being evicted to make room for other, higher-priority workloads. Ignored if \code{use_scaleset=FALSE}.}
|
||||
|
||||
\item{cluster_autoscale}{The autoscaling parameters for the pool. This can be either \code{FALSE}, meaning autoscaling is disabled, or a vector of 2 numbers giving the minimum and maximum size of the agent pool. Ignored if \code{use_scaleset=FALSE}.}
|
||||
|
||||
\item{...}{Other named arguments, to be used as parameters for the agent pool.}
|
||||
}
|
||||
\value{
|
||||
An object of class \code{agent_pool}, suitable for passing to the \code{create_aks} constructor method.
|
||||
}
|
||||
\description{
|
||||
Utility function for specifying Kubernetes agent pools
|
||||
}
|
||||
\details{
|
||||
\code{agent_pool} is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster.
|
||||
}
|
||||
\examples{
|
||||
# pool of 5 Linux GPU-enabled VMs
|
||||
agent_pool("pool1", 5, size="Standard_NC6s_v3")
|
||||
|
||||
# pool of 3 Windows Server VMs, 500GB disk size each
|
||||
agent_pool("pool1", 3, os="Windows", disksize=500)
|
||||
|
||||
# enable cluster autoscaling, with a minimum of 1 and maximum of 10 nodes
|
||||
agent_pool("pool1", 5, cluster_autoscale=c(1, 10))
|
||||
|
||||
# use individual VMs rather than scaleset
|
||||
agent_pool("vmpool1", 3, use_scaleset=FALSE)
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\link{create_aks}, \link{list_vm_sizes}
|
||||
|
||||
\href{https://docs.microsoft.com/en-us/rest/api/aks/managedclusters/createorupdate#managedclusteragentpoolprofile}{Agent pool parameters on Microsoft Docs}
|
||||
}
|
24
man/aks.Rd
24
man/aks.Rd
|
@ -5,10 +5,6 @@
|
|||
\alias{aks}
|
||||
\alias{az_kubernetes_service}
|
||||
\title{Azure Kubernetes Service class}
|
||||
\format{An object of class \code{R6ClassGenerator} of length 24.}
|
||||
\usage{
|
||||
aks
|
||||
}
|
||||
\description{
|
||||
Class representing an Azure Kubernetes Service (AKS) resource. For working with the cluster endpoint itself, including deploying images, creating services etc, see \link{kubernetes_cluster}.
|
||||
}
|
||||
|
@ -18,6 +14,10 @@ The following methods are available, in addition to those provided by the \link[
|
|||
\itemize{
|
||||
\item \code{new(...)}: Initialize a new AKS object.
|
||||
\item \code{get_cluster(config, role)}: Return an object representing the Docker registry endpoint.
|
||||
\item \code{get_agent_pool(pollname)}: Returns an object of class \code{az_agent_pool} representing an agent pool. This class inherits from \link[AzureRMR:az_resource]{AzureRMR::az_resource}; it currently has no extra methods, but these may be added in the future.
|
||||
\item \code{list_agent_pools()}: Returns a list of agent pool objects.
|
||||
\item \code{create_agent_pool(poolname, ..., wait=FALSE)}: Creates a new agent pool. See the \link{agent_pool} function for further arguments to this method.
|
||||
\item \verb{delete_agent_pool(poolname, confirm=TRUE. wait=FALSE)}: Deletes an agent pool.
|
||||
\item \code{list_cluster_resources()}: Returns a list of all the Azure resources managed by the cluster.
|
||||
\item \code{update_aad_password(name=NULL, duration=NULL, ...)}: Update the password for Azure Active Directory integration, returning the new password invisibly. See 'Updating credentials' below.
|
||||
\item \code{update_service_password(name=NULL, duration=NULL, ...)}: Update the password for the service principal used to manage the cluster resources, returning the new password invisibly. See 'Updating credentials' below.
|
||||
|
@ -32,16 +32,16 @@ Note that this class is separate from the Kubernetes cluster itself. This class
|
|||
|
||||
For working with the cluster, including deploying images, services, etc use the object generated with the \code{get_cluster} method. This method takes two optional arguments:
|
||||
\itemize{
|
||||
\item \code{config}: The file in which to store the cluster configuration details. By default, this will be located in the AzureR configuration directory if it exists (see \link[AzureAuth:AzureR_dir]{AzureAuth::AzureR_dir}); otherwise, in the R temporary directory. To use the Kubernetes default \code{~/.kube/config} file, set this argument to NULL. Any existing file in the given location will be overwritten.
|
||||
\item \code{config}: The file in which to store the cluster configuration details. By default, this will be located in the AzureR configuration directory if it exists (see \link[AzureAuth:AzureR_dir]{AzureAuth::AzureR_dir}); otherwise, in the R temporary directory. To use the Kubernetes default \verb{~/.kube/config} file, set this argument to NULL. Any existing file in the given location will be overwritten.
|
||||
\item \code{role}: This can be \code{"User"} (the default) or \code{"Admin"}.
|
||||
}
|
||||
}
|
||||
|
||||
\section{Updating credentials}{
|
||||
|
||||
An AKS cluster requires at least one, and possibly three, service principals. The first service principal is used to manage the resources used by the cluster: the VMs, networking resources, virtual disks, etc. The other two are used for AAD integration. These service principals have secret passwords, which have to be refreshed as they expire.
|
||||
An AKS resource can have up to three service principals associated with it. Two of these are for Azure Active Directory (AAD) integration. The third is used to manage the subsidiary resources (VMs, networks, disks, etc) used by the cluster, if it doesn't have a service identity.
|
||||
|
||||
The \code{update_aad_password()} and \code{update_service_password()} methods let you refresh the passwords for the cluster's service principals. Their arguments are:
|
||||
Any service principals used by the AKS resource will have secret passwords, which have to be refreshed as they expire. The \code{update_aad_password()} and \code{update_service_password()} methods let you refresh the passwords for the cluster's service principals. Their arguments are:
|
||||
\itemize{
|
||||
\item \code{name}: An optional friendly name for the password.
|
||||
\item \code{duration}: The duration for which the new password is valid. Defaults to 2 years.
|
||||
|
@ -64,7 +64,14 @@ myaks$sync_fields()
|
|||
# get the cluster endpoint
|
||||
kubclus <- myaks$get_cluster()
|
||||
|
||||
# refresh the service principal password
|
||||
# list of agent pools
|
||||
myaks$list_agent_pools()
|
||||
|
||||
# create a new agent pool, then delete it
|
||||
pool <- myaks$create_agent_pool("pool2", 3, size="Standard_DS3_v2")
|
||||
pool$delete()
|
||||
|
||||
# refresh the service principal password (mostly for legacy clusters without a managed identity)
|
||||
myaks$update_service_password()
|
||||
|
||||
# refresh the service principal password, using custom credentials to authenticate with MS Graph
|
||||
|
@ -81,4 +88,3 @@ myaks$update_service_password(app="app_id", password="app_password")
|
|||
\href{https://docs.microsoft.com/en-us/azure/aks/}{AKS documentation} and
|
||||
\href{https://docs.microsoft.com/en-us/rest/api/aks/}{API reference}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
% Please edit documentation in R/az_kubernetes_service.R
|
||||
\name{aks_pools}
|
||||
\alias{aks_pools}
|
||||
\title{Utility function for specifying Kubernetes agent pools}
|
||||
\title{Vectorised utility function for specifying Kubernetes agent pools}
|
||||
\usage{
|
||||
aks_pools(name, count, size = "Standard_DS2_v2", os = "Linux")
|
||||
}
|
||||
|
@ -19,10 +19,12 @@ aks_pools(name, count, size = "Standard_DS2_v2", os = "Linux")
|
|||
A list of lists, suitable for passing to the \code{create_aks} constructor method.
|
||||
}
|
||||
\description{
|
||||
Utility function for specifying Kubernetes agent pools
|
||||
Vectorised utility function for specifying Kubernetes agent pools
|
||||
}
|
||||
\details{
|
||||
This is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster. You can specify multiple pools by providing vectors as input arguments; any scalar inputs will be replicated to match.
|
||||
|
||||
\code{aks_pools} is deprecated; please use \link{agent_pool} going forward.
|
||||
}
|
||||
\examples{
|
||||
# 1 pool of 5 Linux VMs
|
||||
|
@ -36,5 +38,5 @@ aks_pools(c("pool1", "pool2"), count=c(3, 3), size=c("Standard_DS2_v2", "Standar
|
|||
|
||||
}
|
||||
\seealso{
|
||||
\link{list_vm_sizes}
|
||||
\link{list_vm_sizes}, \link{agent_pool}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
\alias{call_docker}
|
||||
\title{Call the docker commandline tool}
|
||||
\usage{
|
||||
call_docker(cmd = "", ..., echo = TRUE)
|
||||
call_docker(cmd = "", ..., echo = getOption("azure_containers_tool_echo",
|
||||
TRUE))
|
||||
}
|
||||
\arguments{
|
||||
\item{cmd}{The docker command line to execute.}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
\alias{call_docker_compose}
|
||||
\title{Call the docker-compose commandline tool}
|
||||
\usage{
|
||||
call_docker_compose(cmd = "", ..., echo = TRUE)
|
||||
call_docker_compose(cmd = "", ...,
|
||||
echo = getOption("azure_containers_tool_echo", TRUE))
|
||||
}
|
||||
\arguments{
|
||||
\item{cmd}{The docker-compose command line to execute.}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
\alias{call_helm}
|
||||
\title{Call the Helm commandline tool}
|
||||
\usage{
|
||||
call_helm(cmd = "", config = NULL, ..., echo = TRUE)
|
||||
call_helm(cmd = "", config = NULL, ...,
|
||||
echo = getOption("azure_containers_tool_echo", TRUE))
|
||||
}
|
||||
\arguments{
|
||||
\item{cmd}{The Helm command line to execute.}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
\alias{call_kubectl}
|
||||
\title{Call the Kubernetes commandline tool, kubectl}
|
||||
\usage{
|
||||
call_kubectl(cmd = "", config = NULL, ..., echo = TRUE)
|
||||
call_kubectl(cmd = "", config = NULL, ...,
|
||||
echo = getOption("azure_containers_tool_echo", TRUE))
|
||||
}
|
||||
\arguments{
|
||||
\item{cmd}{The kubectl command line to execute.}
|
||||
|
|
|
@ -17,7 +17,7 @@ Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} cl
|
|||
\itemize{
|
||||
\item \code{name}: The name of the container registry.
|
||||
\item \code{location}: The location/region in which to create the container registry. Defaults to this resource group's location.
|
||||
\item \code{admin_user_enabled}: Whether to enable the Admin user. Currently this must be TRUE to allow Docker to access the registry.
|
||||
\item \code{admin_user_enabled}: Whether to enable the Admin user. Currently this must be \code{TRUE} for ACI to pull from the registry.
|
||||
\item \code{sku}: Either "Basic", "Standard" (the default) or "Premium".
|
||||
\item \code{wait}: Whether to wait until the ACR resource provisioning is complete.
|
||||
\item \code{...}: Other named arguments to pass to the \link{az_resource} initialization function.
|
||||
|
|
|
@ -9,9 +9,10 @@ Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} cl
|
|||
\section{Usage}{
|
||||
\preformatted{create_aks(name, location = self$location,
|
||||
dns_prefix = name, kubernetes_version = NULL,
|
||||
enable_rbac = FALSE, agent_pools = list(),
|
||||
enable_rbac = FALSE, agent_pools = agent_pool("pool1", 3),
|
||||
login_user = "", login_passkey = "",
|
||||
cluster_service_principal = NULL, managed_identity = FALSE,
|
||||
cluster_service_principal = NULL, managed_identity = TRUE,
|
||||
private_cluster = FALSE,
|
||||
properties = list(), ..., wait = TRUE)
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +24,12 @@ Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} cl
|
|||
\item \code{location}: The location/region in which to create the service. Defaults to this resource group's location.
|
||||
\item \code{dns_prefix}: The domain name prefix to use for the cluster endpoint. The actual domain name will start with this argument, followed by a string of pseudorandom characters.
|
||||
\item \code{kubernetes_version}: The Kubernetes version to use. If not specified, uses the most recent version of Kubernetes available.
|
||||
\item \code{enable_rbac}: Whether to enable role-based access controls.
|
||||
\item \code{agent_pools}: A list of pool specifications. See 'Details'.
|
||||
\item \code{login_user,login_passkey}: Optionally, a login username and public key (on Linux). Specify these if you want to be able to ssh into the cluster nodes.
|
||||
\item \code{cluster_service_principal}: The service principal (client) that AKS will use to manage the cluster resources. This should be a list, with the first component being the client ID and the second the client secret. If not supplied, a new service principal will be created (requires an interactive session).
|
||||
\item \code{managed_identity}: Whether the cluster should have a managed identity assigned to it. This is currently in preview; see the \href{https://docs.microsoft.com/en-us/azure/aks/use-managed-identity}{Microsoft Docs page} for enabling this feature.
|
||||
\item \code{enable_rbac}: Whether to enable Kubernetes role-based access controls (which is distinct from Azure AD RBAC).
|
||||
\item \code{agent_pools}: The pool specification(s) for the cluster. See 'Details'.
|
||||
\item \verb{login_user,login_passkey}: Optionally, a login username and public key (on Linux). Specify these if you want to be able to ssh into the cluster nodes.
|
||||
\item \code{cluster_service_principal}: The service principal that AKS will use to manage the cluster resources. This should be a list, with the first component being the client ID and the second the client secret. If not supplied, a new service principal will be created (requires an interactive session). Ignored if \code{managed_identity=TRUE}, which is the default.
|
||||
\item \code{managed_identity}: Whether the cluster should have a managed identity assigned to it. If \code{FALSE}, a service principal will be used to manage the cluster's resources; see 'Details' below.
|
||||
\item \code{private_cluster}: Whether this cluster is private (not visible from the public Internet). A private cluster is accessible only to hosts on its virtual network.
|
||||
\item \code{properties}: A named list of further Kubernetes-specific properties to pass to the initialization function.
|
||||
\item \code{wait}: Whether to wait until the AKS resource provisioning is complete. Note that provisioning a Kubernetes cluster can take several minutes.
|
||||
\item \code{...}: Other named arguments to pass to the initialization function.
|
||||
|
@ -38,9 +40,13 @@ Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} cl
|
|||
|
||||
An AKS resource is a Kubernetes cluster hosted in Azure. See the \link[=aks]{documentation for the resource} for more information. To work with the cluster (deploy images, define and start services, etc) see the \link[=kubernetes_cluster]{documentation for the cluster endpoint}.
|
||||
|
||||
To specify the agent pools for the cluster, it is easiest to use the \link{aks_pools} function. This takes as arguments the name(s) of the pools, the number of nodes, the VM size(s) to use, and the operating system (Windows or Linux) to run on the VMs.
|
||||
The nodes for an AKS cluster are organised into \emph{agent pools}, also known as \emph{node pools}, which are homogenous groups of virtual machines. To specify the details for a single agent pool, use the \code{agent_pool} function, which returns an S3 object of that class. To specify the details for multiple pools, you can supply a list of such objects, or a single call to the \code{aks_pools} function; see the examples below. Note that \code{aks_pools} is older, and does not support all the possible parameters for an agent pool.
|
||||
|
||||
By default, the password for a newly-created service principal will expire after one year. You can run the \code{update_service_password} method of the AKS object to reset/update the password before it expires.
|
||||
Of the agent pools in a cluster, at least one must be a \emph{system pool}, which is used to host critical system pods such as CoreDNS and tunnelfront. If you specify more than one pool, the first pool will be treated as the system pool. Note that there are certain \href{https://docs.microsoft.com/en-us/azure/aks/use-system-pools}{extra requirements} for the system pool.
|
||||
|
||||
An AKS cluster requires an identity to manage the low-level resources it uses, such as virtual machines and networks. The default and recommended method is to use a \emph{managed identity}, in which all the details of this process are handled by AKS. In AzureContainers version 1.2.1 and older, a \emph{service principal} was used instead, which is an older and less automated method. By setting \code{managed_identity=FALSE}, you can continue using a service principal instead of a managed identity.
|
||||
|
||||
One thing to be aware of with service principals is that they have a secret password that will expire eventually. By default, the password for a newly-created service principal will expire after one year. You should run the \code{update_service_password} method of the AKS object to reset/update the password before it expires.
|
||||
}
|
||||
|
||||
\section{Value}{
|
||||
|
@ -55,15 +61,25 @@ rg <- AzureRMR::get_azure_login()$
|
|||
get_subscription("subscription_id")$
|
||||
get_resource_group("rgname")
|
||||
|
||||
rg$create_aks("mycluster", agent_pools=aks_pools("pool1", 5))
|
||||
rg$create_aks("mycluster", agent_pools=agent_pool("pool1", 5))
|
||||
|
||||
# GPU-enabled cluster
|
||||
rg$create_aks("mygpucluster", agent_pools=aks_pools("pool1", 5, size="Standard_NC6s_v3"))
|
||||
rg$create_aks("mygpucluster", agent_pools=agent_pool("pool1", 5, size="Standard_NC6s_v3"))
|
||||
|
||||
# multiple agent pools
|
||||
rg$create_aks("mycluster", agent_pools=list(
|
||||
agent_pool("pool1", 2),
|
||||
agent_pool("pool2", 3, size="Standard_NC6s_v3")
|
||||
))
|
||||
|
||||
# deprecated alternative for multiple pools
|
||||
rg$create_aks("mycluster",
|
||||
agent_pools=aks_pools(c("pool1", "pool2"), c(2, 3), c("Standard_DS2_v2", "Standard_NC6s_v3")))
|
||||
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\link{get_aks}, \link{delete_aks}, \link{list_aks}, \link{aks_pools}
|
||||
\link{get_aks}, \link{delete_aks}, \link{list_aks}, \link{agent_pool}, \link{aks_pools}
|
||||
|
||||
\link{az_kubernetes_service}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
kubernetes_cluster(config = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{config}{The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \code{~/.kube/config} file.}
|
||||
\item{config}{The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \verb{~/.kube/config} file.}
|
||||
}
|
||||
\value{
|
||||
An R6 object of class \code{KubernetesCluster}.
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hello
|
||||
name: hellodep
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hellodep
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hello
|
||||
app: hellodep
|
||||
spec:
|
||||
containers:
|
||||
- name: acrname
|
||||
|
@ -18,10 +21,10 @@ spec:
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: hello-svc
|
||||
name: hellodep-svc
|
||||
spec:
|
||||
selector:
|
||||
app: hello
|
||||
app: hellodep
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
make_name <- function(n=20)
|
||||
{
|
||||
paste0(sample(letters, n, TRUE), collapse="")
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
context("Resource group creation")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
Sys.setenv(AZ_TEST_RG=paste(sample(letters, 20, replace=TRUE), collapse=""))
|
||||
Sys.setenv(AZ_TEST_ACR=paste(sample(letters, 10, replace=TRUE), collapse=""))
|
||||
|
||||
test_that("Resource group creation succeeds",
|
||||
{
|
||||
sub <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)
|
||||
|
||||
rgname <- Sys.getenv("AZ_TEST_RG")
|
||||
|
||||
expect_false(sub$resource_group_exists(rgname))
|
||||
rg <- sub$create_resource_group(rgname, location="australiaeast")
|
||||
expect_true(sub$resource_group_exists(rgname))
|
||||
})
|
||||
|
|
@ -8,15 +8,16 @@ subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
|||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
acrname <- Sys.getenv("AZ_TEST_ACR")
|
||||
if(acrname == "")
|
||||
skip("ACR tests skipped: resource name not set")
|
||||
|
||||
rgname <- Sys.getenv("AZ_TEST_RG")
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
get_resource_group(rgname)
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
acrname <- make_name(10)
|
||||
|
||||
test_that("ACR works",
|
||||
{
|
||||
|
@ -67,3 +68,8 @@ test_that("ACR works with app login",
|
|||
|
||||
expect_equal(reg$list_repositories(), c("hello-world", "hello-world-sp"))
|
||||
})
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
||||
|
|
|
@ -8,19 +8,33 @@ subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
|||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
acrname <- Sys.getenv("AZ_TEST_ACR")
|
||||
if(acrname == "")
|
||||
skip("ACI tests skipped: resource names not set")
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
test_that("ACI works",
|
||||
{
|
||||
rgname <- Sys.getenv("AZ_TEST_RG")
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
get_resource_group(rgname)
|
||||
acrname <- make_name(10)
|
||||
acr <- rg$create_acr(acrname, admin_user_enabled=TRUE)
|
||||
reg <- acr$get_docker_registry(as_admin=TRUE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
expect_false(is.null(reg$username) || is.null(reg$password))
|
||||
|
||||
aciname <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ."
|
||||
call_docker(cmdline)
|
||||
|
||||
reg$push("hello-world")
|
||||
|
||||
cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world")
|
||||
call_docker(cmdline)
|
||||
|
||||
# from local image
|
||||
aciname <- make_name(10)
|
||||
expect_true(is_aci(rg$create_aci(aciname,
|
||||
image="hello-world")))
|
||||
|
||||
|
@ -32,23 +46,25 @@ test_that("ACI works",
|
|||
expect_silent(aci$start())
|
||||
expect_silent(aci$restart())
|
||||
|
||||
acr <- rg$get_acr(acrname)
|
||||
expect_true(is_acr(acr))
|
||||
reg <- acr$get_docker_registry(as_admin=TRUE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
expect_false(is.null(reg$username) || is.null(reg$password))
|
||||
|
||||
aciname2 <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
# from Resource Manager object
|
||||
aciname2 <- make_name(10)
|
||||
aci2 <- rg$create_aci(aciname2,
|
||||
image=paste0(reg$server$hostname, "/hello-world"),
|
||||
registry_creds=reg)
|
||||
|
||||
expect_true(is_aci(aci2))
|
||||
|
||||
aciname3 <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
# from Docker registry object
|
||||
aciname3 <- make_name(10)
|
||||
aci3 <- rg$create_aci(aciname3,
|
||||
image=paste0(reg$server$hostname, "/hello-world-sp"),
|
||||
image=paste0(reg$server$hostname, "/hello-world"),
|
||||
registry_creds=aci_creds(reg$server$hostname, app, password))
|
||||
|
||||
expect_true(is_aci(aci3))
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
context("AKS interface")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
acrname <- Sys.getenv("AZ_TEST_ACR")
|
||||
if(acrname == "")
|
||||
skip("AKS tests skipped: resource names not set")
|
||||
|
||||
rgname <- Sys.getenv("AZ_TEST_RG")
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
get_resource_group(rgname)
|
||||
|
||||
acr <- rg$get_acr(acrname)
|
||||
|
||||
|
||||
test_that("AKS works",
|
||||
{
|
||||
expect_true(is_acr(acr))
|
||||
|
||||
reg <- acr$get_docker_registry(as_admin=TRUE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
expect_false(is.null(reg$username) || is.null(reg$password))
|
||||
|
||||
expect_is(rg$list_kubernetes_versions(), "character")
|
||||
|
||||
aksname <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
expect_true(is_aks(rg$create_aks(aksname, agent_pools=aks_pools("pool1", 2))))
|
||||
|
||||
expect_true(is_aks(rg$list_aks()[[1]]))
|
||||
aks <- rg$get_aks(aksname)
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
aks$update_service_password()
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
clus$create_registry_secret(reg, email="me@example.com")
|
||||
clus$create(hello_yaml)
|
||||
})
|
||||
|
||||
|
||||
test_that("AKS works with RBAC",
|
||||
{
|
||||
aksname <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
aks <- rg$create_aks(aksname, agent_pools=aks_pools("pool1", 2))
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
acr$add_role_assignment(aks, "Acrpull")
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
clus$create(hello_yaml)
|
||||
})
|
|
@ -0,0 +1,56 @@
|
|||
context("AKS interface with managed identity")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
test_that("AKS works with managed identity",
|
||||
{
|
||||
aksname <- make_name(10)
|
||||
|
||||
expect_is(rg$list_kubernetes_versions(), "character")
|
||||
|
||||
expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=TRUE)))
|
||||
expect_true(is_aks(rg$list_aks()[[1]]))
|
||||
aks <- rg$get_aks(aksname)
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
# no SP password with svc identity
|
||||
expect_error(aks$update_service_password())
|
||||
|
||||
pool1 <- aks$get_agent_pool("pool1")
|
||||
expect_is(pool1, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
pool2 <- aks$create_agent_pool("pool2", 1, disksize=500, wait=TRUE)
|
||||
expect_is(pool2, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 2 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
expect_message(pool2$delete(confirm=FALSE))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -0,0 +1,78 @@
|
|||
context("AKS-ACR interop with managed identity")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
aksname <- make_name(10)
|
||||
aks <- rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=TRUE)
|
||||
|
||||
test_that("AKS/ACR works with managed identity",
|
||||
{
|
||||
acrname <- make_name(10)
|
||||
acr <- rg$create_acr(acrname, admin_user_enabled=TRUE)
|
||||
reg <- acr$get_docker_registry(as_admin=TRUE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
|
||||
cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ."
|
||||
call_docker(cmdline)
|
||||
|
||||
reg$push("hello-world")
|
||||
|
||||
cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world")
|
||||
call_docker(cmdline)
|
||||
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
clus$create_registry_secret(reg, email="me@example.com")
|
||||
clus$create(hello_yaml)
|
||||
})
|
||||
|
||||
|
||||
test_that("AKS/ACR works with managed identity/RBAC",
|
||||
{
|
||||
acrname <- make_name(10)
|
||||
acr <- rg$create_acr(acrname, admin_user_enabled=FALSE)
|
||||
reg <- acr$get_docker_registry(as_admin=FALSE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
|
||||
cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ."
|
||||
call_docker(cmdline)
|
||||
|
||||
reg$push("hello-world")
|
||||
|
||||
cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world")
|
||||
call_docker(cmdline)
|
||||
|
||||
acr$add_role_assignment(aks, "Acrpull")
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
hello_yaml <- gsub("hellodep", "hellodep-rb", hello_yaml)
|
||||
clus$create(hello_yaml)
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -0,0 +1,51 @@
|
|||
context("AKS interface with service principal")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
test_that("AKS works with service principal",
|
||||
{
|
||||
aksname <- paste0(sample(letters, 10, TRUE), collapse="")
|
||||
expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=FALSE)))
|
||||
aks <- rg$get_aks(aksname)
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
expect_message(aks$update_service_password())
|
||||
|
||||
pool1 <- aks$get_agent_pool("pool1")
|
||||
expect_is(pool1, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
pool2 <- aks$create_agent_pool("pool2", 1, disksize=500, wait=TRUE)
|
||||
expect_is(pool2, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 2 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
expect_message(pool2$delete(confirm=FALSE))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -0,0 +1,78 @@
|
|||
context("AKS-ACR interop with service principal")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
aksname <- make_name(10)
|
||||
aks <- rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=FALSE)
|
||||
|
||||
test_that("AKS/ACR works with service principal",
|
||||
{
|
||||
acrname <- make_name(10)
|
||||
acr <- rg$create_acr(acrname, admin_user_enabled=TRUE)
|
||||
reg <- acr$get_docker_registry(as_admin=TRUE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
|
||||
cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ."
|
||||
call_docker(cmdline)
|
||||
|
||||
reg$push("hello-world")
|
||||
|
||||
cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world")
|
||||
call_docker(cmdline)
|
||||
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
clus$create_registry_secret(reg, email="me@example.com")
|
||||
clus$create(hello_yaml)
|
||||
})
|
||||
|
||||
|
||||
test_that("AKS/ACR works with service principal/RBAC",
|
||||
{
|
||||
acrname <- make_name(10)
|
||||
acr <- rg$create_acr(acrname, admin_user_enabled=FALSE)
|
||||
reg <- acr$get_docker_registry(as_admin=FALSE)
|
||||
expect_true(is_docker_registry(reg))
|
||||
|
||||
cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ."
|
||||
call_docker(cmdline)
|
||||
|
||||
reg$push("hello-world")
|
||||
|
||||
cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world")
|
||||
call_docker(cmdline)
|
||||
|
||||
acr$add_role_assignment(aks, "Acrpull")
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
|
||||
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
|
||||
hello_yaml <- gsub("hellodep", "hellodep-rb", hello_yaml)
|
||||
clus$create(hello_yaml)
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
context("AKS interface with availability set")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
test_that("AKS works with availability set",
|
||||
{
|
||||
aksname <- make_name(10)
|
||||
expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1, use_scaleset=FALSE),
|
||||
managed_identity=TRUE)))
|
||||
|
||||
aks <- rg$get_aks(aksname)
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
pool1 <- aks$get_agent_pool("pool1")
|
||||
expect_is(pool1, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
expect_error(aks$create_agent_pool("pool2", 1, disksize=500, wait=TRUE))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
context("AKS interface with managed identity/private cluster")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
rgname <- make_name(10)
|
||||
rg <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)$
|
||||
create_resource_group(rgname, location="australiaeast")
|
||||
|
||||
echo <- getOption("azure_containers_tool_echo")
|
||||
options(azure_containers_tool_echo=FALSE)
|
||||
|
||||
test_that("AKS works with private cluster",
|
||||
{
|
||||
aksname <- make_name(10)
|
||||
expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1),
|
||||
managed_identity=TRUE, private_cluster=TRUE)))
|
||||
aks <- rg$get_aks(aksname)
|
||||
expect_true(is_aks(aks))
|
||||
|
||||
# no SP password with svc identity
|
||||
expect_error(aks$update_service_password())
|
||||
|
||||
pool1 <- aks$get_agent_pool("pool1")
|
||||
expect_is(pool1, "az_agent_pool")
|
||||
|
||||
pools <- aks$list_agent_pools()
|
||||
expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool")))
|
||||
|
||||
clus <- aks$get_cluster()
|
||||
expect_true(is_kubernetes_cluster(clus))
|
||||
})
|
||||
|
||||
|
||||
teardown({
|
||||
options(azure_containers_tool_echo=echo)
|
||||
suppressMessages(rg$delete(confirm=FALSE))
|
||||
})
|
|
@ -1,21 +0,0 @@
|
|||
context("Resource group deletion")
|
||||
|
||||
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
|
||||
app <- Sys.getenv("AZ_TEST_APP_ID")
|
||||
password <- Sys.getenv("AZ_TEST_PASSWORD")
|
||||
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Tests skipped: ARM credentials not set")
|
||||
|
||||
test_that("Resource group deletion succeeds",
|
||||
{
|
||||
sub <- AzureRMR::az_rm$
|
||||
new(tenant=tenant, app=app, password=password)$
|
||||
get_subscription(subscription)
|
||||
|
||||
rgname <- Sys.getenv("AZ_TEST_RG")
|
||||
|
||||
expect_true(sub$resource_group_exists(rgname))
|
||||
sub$delete_resource_group(rgname, confirm=FALSE)
|
||||
})
|
|
@ -24,7 +24,7 @@ library(randomForest)
|
|||
bos_rf <- randomForest(medv ~ ., data=Boston, ntree=100)
|
||||
|
||||
# save the model
|
||||
saveRDS(bos.rf, "bos_rf.rds")
|
||||
saveRDS(bos_rf, "bos_rf.rds")
|
||||
```
|
||||
|
||||
|
||||
|
@ -58,7 +58,6 @@ Let's package up the model and the scoring script into a Docker image. A Dockerf
|
|||
# example Dockerfile to expose a plumber service
|
||||
|
||||
FROM trestletech/plumber
|
||||
MAINTAINER Hong Ooi <hongooi@microsoft.com>
|
||||
|
||||
# install the randomForest package
|
||||
RUN R -e 'install.packages(c("randomForest"))'
|
||||
|
@ -118,13 +117,6 @@ deployaci <- deployresgrp$create_aci("deployaci",
|
|||
ports=aci_ports(8000))
|
||||
```
|
||||
|
||||
To check on the progress of the deployment, call the object's `sync_fields()` method. This will update and return its provisioning state, which will be `"Creating"` while the instance is being created, and `"Succeeded"` once it is running. Deploying a simple container like this usually takes less than a minute.
|
||||
|
||||
```r
|
||||
deployaci$sync_fields()
|
||||
#> [1] "Succeeded"
|
||||
```
|
||||
|
||||
Once the instance is running, let's call the prediction API with some sample data. By default, AzureContainers will assign the container a domain name with prefix taken from the instance name. The port is 8000 as specified in the Dockerfile, and the URI path is `/score` indicating we want to call the scoring function defined earlier.
|
||||
|
||||
The data to be scored---the first 10 rows of the Boston dataset---is passed in the _body_ of the request as a named list, encoded as JSON. A feature of Plumber is that, when the body of the request is in this format, it will extract the elements of the list and pass them to the scoring function as named arguments. This makes it easy to pass around relatively large amounts of data, eg if the data is wide, or for scoring multiple rows at a time. For more information on how to create and interact with Plumber APIs, consult the [Plumber documentation](https://www.rplumber.io/docs/).
|
||||
|
@ -132,7 +124,7 @@ The data to be scored---the first 10 rows of the Boston dataset---is passed in t
|
|||
```r
|
||||
response <- httr::POST("http://deployaci.australiaeast.azurecontainer.io:8000/score",
|
||||
body=list(df=MASS::Boston[1:10,]), encode="json")
|
||||
httr::content(response, flatten=TRUE)
|
||||
httr::content(response, simplifyVector=TRUE)
|
||||
#> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785
|
||||
```
|
||||
|
||||
|
@ -143,7 +135,7 @@ Deploying a service to a container instance is simple, but lacks many features t
|
|||
|
||||
```r
|
||||
# create a Kubernetes cluster with 2 nodes, running Linux (the default)
|
||||
deployclus_svc <- deployresgrp$create_aks("deployclus", agent_pools=aks_pools("pool1", 2))
|
||||
deployclus_svc <- deployresgrp$create_aks("deployclus", agent_pools=agent_pool("pool1", 2))
|
||||
```
|
||||
|
||||
Unlike an ACI resource, creating a Kubernetes cluster can take several minutes. By default, the `create_aks()` method will wait until the cluster provisioning is complete before it returns.
|
||||
|
@ -151,11 +143,14 @@ Unlike an ACI resource, creating a Kubernetes cluster can take several minutes.
|
|||
Having created the cluster, we can deploy our model and create a service. We'll use a YAML configuration file to specify the details for the deployment and service API. The image to be deployed is the same as before.
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bos-rf
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: bos-rf
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
|
@ -192,9 +187,7 @@ The following code will obtain the cluster endpoint from the AKS resource and th
|
|||
|
||||
```r
|
||||
# grant the cluster pull access to the registry
|
||||
gr <- AzureGraph::get_graph_login()
|
||||
aks_app_id <- deployclus$properties$servicePrincipalProfile$clientID
|
||||
reg$add_role_assignment(gr$get_app(aks_app_id), "Acrpull")
|
||||
deployreg_svc$add_role_assignment(deployclus_svc, "Acrpull")
|
||||
|
||||
# get the cluster endpoint
|
||||
deployclus <- deployclus_svc$get_cluster()
|
||||
|
@ -211,22 +204,22 @@ deployclus$get("deployment bos-rf")
|
|||
#> NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
#> bos-rf 1 1 1 1 5m
|
||||
|
||||
deployclus$get("service bos-rf-svc")
|
||||
svc <- read.table(text=deployclus$get("service bos-rf-svc")$stdout, header=TRUE)
|
||||
#> Kubernetes operation: get service bos-rf-svc --kubeconfig=".../kubeconfigxxxx"
|
||||
#> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
#> bos-rf-svc LoadBalancer 10.0.8.189 52.187.249.58 8000:32276/TCP 5m
|
||||
```
|
||||
|
||||
Once the service is up and running, as indicated by the presence of an external IP in the service details, let's test it with a HTTP request. The response should be the same as it was with the container instance.
|
||||
Once the service is up and running, as indicated by the presence of an external IP in the service details, let's test it with a HTTP request. The response should be the same as it was with the container instance. Notice how we extract the IP address from the service details above.
|
||||
|
||||
```r
|
||||
response <- httr::POST("http://52.187.249.58:8000/score",
|
||||
response <- httr::POST(paste0("http://", svc$EXTERNAL.IP[1], ":8000/score"),
|
||||
body=list(df=MASS::Boston[1:10,]), encode="json")
|
||||
httr::content(response, simplifyVector=TRUE)
|
||||
#> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785
|
||||
```
|
||||
|
||||
Finally, once we are done, we can tear down the service and deployment:
|
||||
Finally, once we are done, we can tear down the service and deployment. Depending on the version of Kubernetes the cluster is running, deleting the service may take a few minutes.
|
||||
|
||||
```r
|
||||
deployclus$delete("service", "bos-rf-svc")
|
||||
|
@ -239,6 +232,10 @@ And if required, we can also delete all the resources created here, by simply de
|
|||
deployresgrp$delete()
|
||||
```
|
||||
|
||||
### Security note
|
||||
|
||||
One important thing to note about the above example is that it is **insecure**. The Plumber service is exposed over HTTP, and there is no authentication layer: anyone on the Internet can contact the service and interact with it. Therefore, it's highly recommended that you should provide at least some level of authentication, as well as restricting the service to HTTPS only. You can also create the AKS resource as a private cluster; however, be aware that if you do this, you can only interact with the cluster endpoint from a host which is on the cluster's own subnet.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче