Skip to content

Instantly share code, notes, and snippets.

@ninjarobot
Last active December 19, 2021 19:23
Show Gist options
  • Save ninjarobot/e4554b7962c13afbfd0fb0a9dd568a1b to your computer and use it in GitHub Desktop.
Save ninjarobot/e4554b7962c13afbfd0fb0a9dd568a1b to your computer and use it in GitHub Desktop.
Full workflow for Azure container apps with Farmer - build and push container, deploy to app.
#r "../../src/Farmer/bin/Debug/net5.0/Farmer.dll"
#r "nuget: FSharp.Text.Docker"
open System
open Farmer
open Farmer.ContainerApp
open Farmer.Builders
open FSharp.Text.Docker.Builders
// Create Container Registry
let myAcr =
containerRegistry {
name "farmercontainers"
sku ContainerRegistry.Basic
enable_admin_user
}
// Some quick app we want to run. In the real world, you'll pull this source when building
// the image, but here we will just embed it.
let fsharpAppSource = """
#r "nuget: Suave, Version=2.6.0"
open Suave
let config = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ] }
startWebServer config (Successful.OK "Hello Container App Farmers!")
"""
let encodedSource = fsharpAppSource |> System.Text.Encoding.UTF8.GetBytes |> Convert.ToBase64String
// Define Docker image
let dockerfile =
dockerfile {
from "mcr.microsoft.com/dotnet/sdk:5.0.404"
expose [8080]
run $"echo {encodedSource} | base64 -d > main.fsx"
cmd "dotnet fsi main.fsx"
}
let encodedDockerfile = dockerfile.Build() |> System.Text.Encoding.UTF8.GetBytes |> Convert.ToBase64String
let scriptIdentity = createUserAssignedIdentity "deployment-identity"
// Build and push to that ACR from a deploymentScript
let buildImage =
deploymentScript {
name "build-image"
env_vars [ "ACR_NAME", "farmercontainers" ]
identity scriptIdentity
depends_on myAcr
script_content (
[
"set -eux"
$"echo {encodedDockerfile} | base64 -d > Dockerfile"
$"az acr build --registry $ACR_NAME --image fsharpwebapp/http:1.0.0 ."
] |> String.concat " ; "
)
}
/// Deploy a container app after the image is built. It will reference the container registry to get credentials.
let farmerlogs = logAnalytics { name "farmerlogs" }
let containerRegistryDomain = "myregistry.azurecr.io"
let version = "1.0.0"
let containerEnv =
containerEnvironment {
name "farmercontainers"
depends_on buildImage
log_analytics_instance farmerlogs
add_containers [
containerApp {
name "http"
active_revision_mode ActiveRevisionsMode.Single
private_docker_image $"{myAcr.Name.Value}.azurecr.io" myAcr.Name.Value "fsharpwebapp" version
replicas 1 5
ingress_visibility External
ingress_target_port 8080us
ingress_transport Auto
dapr_app_id "http"
add_scale_rule "http-rule" (ScaleRule.Http { ConcurrentRequests = 100 })
}
// What I'd like it to look like
(*
containerApp {
name "http"
active_revision_mode ActiveRevisionsMode.Single
add_registry_credentials [ myAcr ]
add_containers [ // An app can have multiple containers in it - a kubernetes pod
private_docker_image $"{myAcr.Name.Value}.azurecr.io" "fsharpwebapp" version
private_docker_image $"{myAcr.Name.Value}.azurecr.io" "someothercontainer" version
private_docker_image $"{myAcr.Name.Value}.azurecr.io" "andanothercontainer" version
public_docker_image "nginx:latest"
]
private_docker_image $"{myAcr.Name.Value}.azurecr.io" myAcr.Name.Value "fsharpwebapp" version
replicas 1 5
ingress_visibility External
ingress_target_port 8080us
ingress_transport Auto
dapr_app_id "http"
add_scale_rule "http-rule" (ScaleRule.Http { ConcurrentRequests = 100 })
}
*)
]
}
/// Deployment template to orchestrate all of these.
let template =
arm {
location Location.EastUS
add_resources [
scriptIdentity
farmerlogs
myAcr
buildImage
containerEnv
]
}
template.Template |> Writer.toJson |> Console.WriteLine
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {},
"resources": [
{
"apiVersion": "2018-11-30",
"dependsOn": [],
"location": "eastus",
"name": "deployment-identity",
"tags": {},
"type": "Microsoft.ManagedIdentity/userAssignedIdentities"
},
{
"apiVersion": "2020-03-01-preview",
"location": "eastus",
"name": "farmerlogs",
"properties": {
"sku": {
"name": "PerGB2018"
}
},
"tags": {},
"type": "Microsoft.OperationalInsights/workspaces"
},
{
"apiVersion": "2019-05-01",
"location": "eastus",
"name": "farmercontainers",
"properties": {
"adminUserEnabled": true
},
"sku": {
"name": "Basic"
},
"tags": {},
"type": "Microsoft.ContainerRegistry/registries"
},
{
"apiVersion": "2021-04-01-preview",
"dependsOn": [],
"name": "[guid(concat(resourceGroup().id, 'b24988ac-6180-42a0-ab88-20f7382dd24c'))]",
"properties": {
"principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'deployment-identity')).principalId]",
"principalType": "ServicePrincipal",
"roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]"
},
"type": "Microsoft.Authorization/roleAssignments"
},
{
"apiVersion": "2019-10-01-preview",
"dependsOn": [
"[resourceId('Microsoft.ContainerRegistry/registries', 'farmercontainers')]",
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'deployment-identity')]"
],
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'deployment-identity')]": {}
}
},
"kind": "AzureCLI",
"location": "eastus",
"name": "build-image",
"properties": {
"azCliVersion": "2.9.1",
"cleanupPreference": "Always",
"environmentVariables": [
{
"name": "ACR_NAME",
"value": "farmercontainers"
}
],
"retentionInterval": "P1D",
"scriptContent": "set -eux ; echo RlJPTSBtY3IubWljcm9zb2Z0LmNvbS9kb3RuZXQvc2RrOjUuMC40MDQKRVhQT1NFIDgwODAKUlVOIGVjaG8gQ2lOeUlDSnVkV2RsZERvZ1UzVmhkbVVzSUZabGNuTnBiMjQ5TWk0MkxqQWlDbTl3Wlc0Z1UzVmhkbVVLYkdWMElHTnZibVpwWnlBOUlIc2daR1ZtWVhWc2RFTnZibVpwWnlCM2FYUm9JR0pwYm1ScGJtZHpJRDBnV3lCSWRIUndRbWx1WkdsdVp5NWpjbVZoZEdWVGFXMXdiR1VnU0ZSVVVDQWlNQzR3TGpBdU1DSWdPREE0TUNCZElIMEtjM1JoY25SWFpXSlRaWEoyWlhJZ1kyOXVabWxuSUNoVGRXTmpaWE56Wm5Wc0xrOUxJQ0pJWld4c2J5QkRiMjUwWVdsdVpYSWdRWEJ3SUVaaGNtMWxjbk1oSWlrSyB8IGJhc2U2NCAtZCA+IG1haW4uZnN4CkNNRCBkb3RuZXQgZnNpIG1haW4uZnN4 | base64 -d > Dockerfile ; az acr build --registry $ACR_NAME --image fsharpwebapp:1.0.0 ."
},
"tags": {},
"type": "Microsoft.Resources/deploymentScripts"
},
{
"apiVersion": "2021-02-01",
"dependsOn": [
"[resourceId('Microsoft.OperationalInsights/workspaces', 'farmerlogs')]",
"[resourceId('Microsoft.Resources/deploymentScripts', 'build-image')]"
],
"kind": "containerenvironment",
"location": "eastus",
"name": "farmercontainers",
"properties": {
"appLogsConfiguration": {
"destination": "log-analytics",
"logAnalyticsConfiguration": {
"customerId": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', 'farmerlogs'), '2020-03-01-preview').customerId]",
"sharedKey": "[listkeys(resourceId('Microsoft.OperationalInsights/workspaces', 'farmerlogs'), '2020-03-01-preview').primarySharedKey]"
}
},
"internalLoadBalancerEnabled": false,
"type": "managed"
},
"tags": {},
"type": "Microsoft.Web/kubeEnvironments"
},
{
"apiVersion": "2021-03-01",
"dependsOn": [
"[resourceId('Microsoft.Web/kubeEnvironments', 'farmercontainers')]"
],
"kind": "containerapp",
"location": "eastus",
"name": "http",
"properties": {
"configuration": {
"activeRevisionsMode": "Single",
"ingress": {
"external": true,
"targetPort": 8080,
"transport": "auto"
},
"registries": [
{
"passwordSecretRef": "container-registry-password-for-farmercontainers",
"server": "farmercontainers.azurecr.io",
"username": "farmercontainers"
}
],
"secrets": [
{
"name": "container-registry-password-for-farmercontainers",
"value": "[listCredentials(resourceId('Microsoft.ContainerRegistry/registries','farmercontainers'),'2019-05-01').passwords[0].value]"
}
]
},
"kubeEnvironmentId": "[resourceId('Microsoft.Web/kubeEnvironments', 'farmercontainers')]",
"template": {
"containers": [
{
"env": [],
"image": "farmercontainers.azurecr.io/farmercontainers/fsharpwebapp:1.0.0",
"name": "http",
"resources": {
"cpu": 0.25,
"memory": "0.50Gi"
}
},
{
"env": [],
"image": "nginx:latest",
"name": "http-frontend",
"resources": {
"cpu": 0.25,
"memory": "0.50Gi"
}
}
],
"dapr": {
"enabled": false
},
"scale": {
"maxReplicas": 5,
"minReplicas": 1,
"rules": [
{
"http": {
"metadata": {
"concurrentRequests": "100"
}
},
"name": "http-rule"
}
]
}
}
},
"type": "Microsoft.Web/containerApps"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment