Last active
December 19, 2021 19:23
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"$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