Levering Logic apps to do ops stuff on AKS - Part 2
Hi!
This is the second part of my serie about AKS and Logic Apps.
In part 1, we just explore how to use a logic app to start an AKS cluster. We were thus capable of validating that the idea is working. Now we want to go a little further than that. First, we will add some intelligence in our logic app to get AKS cluster related information dynamically.
And second we wil create another logic app to automate the cluster certificate rotation.
Let’s get going!
Table of content
- Adding intellignce to our Start/Stop AKS logic app
- Automate AKS Certificate rotation
- Take Away
1. Adding intelligence to our Start/Stop AKS logic app
Ok, we stopped last time with a workflow like that:
A very simple workflow, in which we put manually the aks cluster name.
Not very dynamic!
We did however already added a List resources by resource group action
.
This is the basis to make our workflow more dynamic.
After running, we can check the row input:
{
"method": "get",
"queries": {
"x-ms-api-version": "2016-06-01"
},
"path": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/resources",
"host": {
"connection": {
"name": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetupkured1/providers/Microsoft.Web/connections/arm"
}
}
}
and output:
{
"statusCode": 200,
"headers": {
"Pragma": "no-cache",
"x-ms-ratelimit-remaining-subscription-reads": "11996",
"x-ms-request-id": "ec7132da-ab79-4a40-8eb9-40d531b1fc8b",
"x-ms-correlation-request-id": "ec7132da-ab79-4a40-8eb9-40d531b1fc8b",
"x-ms-routing-request-id": "WESTEUROPE:20211215T121809Z:ec7132da-ab79-4a40-8eb9-40d531b1fc8b",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"Timing-Allow-Origin": "*",
"x-ms-apihub-cached-response": "true",
"Cache-Control": "no-cache",
"Date": "Wed, 15 Dec 2021 12:18:09 GMT",
"Content-Length": "7653",
"Content-Type": "application/json",
"Expires": "-1"
},
"body": {
"value": [
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.ContainerService/managedClusters/aks-1",
"name": "aks-1",
"type": "Microsoft.ContainerService/managedClusters",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Insights/activityLogAlerts/malt-ListAKSAdminCredsEvent-aks-1",
"name": "malt-ListAKSAdminCredsEvent-aks-1",
"type": "Microsoft.Insights/activityLogAlerts",
"location": "global",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Insights/metricalerts/malt-NodeCPUPercentageThreshold-aks-1",
"name": "malt-NodeCPUPercentageThreshold-aks-1",
"type": "Microsoft.Insights/metricalerts",
"location": "global",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Insights/metricalerts/malt-NodeDiskPercentageThreshold-aks-1",
"name": "malt-NodeDiskPercentageThreshold-aks-1",
"type": "Microsoft.Insights/metricalerts",
"location": "global",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Insights/metricalerts/malt-NodeWorkingSetMemoryPercentageThreshold-aks-1",
"name": "malt-NodeWorkingSetMemoryPercentageThreshold-aks-1",
"type": "Microsoft.Insights/metricalerts",
"location": "global",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Insights/metricalerts/malt-UnschedulablePodCountThreshold-aks-1",
"name": "malt-UnschedulablePodCountThreshold-aks-1",
"type": "Microsoft.Insights/metricalerts",
"location": "global",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uai-agwagicmeetup1",
"name": "uai-agwagicmeetup1",
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/uaiaks-1",
"name": "uaiaks-1",
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"location": "westeurope",
"tags": {
"CostCenter": "tflab",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "tfmodule",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/applicationGateways/agwagicmeetup1",
"name": "agwagicmeetup1",
"type": "Microsoft.Network/applicationGateways",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/networkSecurityGroups/nsg-agwagicmeetup1",
"name": "nsg-agwagicmeetup1",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/networkSecurityGroups/nsg-subBEagicmeetup1",
"name": "nsg-subBEagicmeetup1",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/networkSecurityGroups/nsg-subFEagicmeetup1",
"name": "nsg-subFEagicmeetup1",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/publicIPAddresses/pubip-agwagicmeetup1",
"name": "pubip-agwagicmeetup1",
"type": "Microsoft.Network/publicIPAddresses",
"sku": {
"name": "Standard"
},
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
},
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.Network/virtualNetworks/vnetagicmeetup1",
"name": "vnetagicmeetup1",
"type": "Microsoft.Network/virtualNetworks",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
}
]
}
}
We want to get only the AKS cluster so we will add a filter:
The logic app will gently proposes us some dynamic content. We will put the value
of List resource by resource group
action in the From
and filter on the Type
that we will want to be equal to Microsoft.containerService/ManagedCluster
The result output will be the AKS cluster:
{
"body": [
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rsgagicmeetup1/providers/Microsoft.ContainerService/managedClusters/aks-1",
"name": "aks-1",
"type": "Microsoft.ContainerService/managedClusters",
"location": "westeurope",
"tags": {
"CostCenter": "labaks",
"Country": "fr",
"Environment": "lab",
"ManagedBy": "Terraform",
"Project": "agic",
"ResourceOwner": "That would be me"
}
}
]
}
With that we can now change the Invoke resource operation
with its static value to something more dynamic. To do so, we first add a For each
control action
And select as the output the Body
of our filter
action
Then we put our Invoke resource operation
inside our For each
and use the dynamic content to get the name
We also want to add a notification so we can add Teams Post message
action before and after. Again we use dynamic content in the message
And that’s it, we have a nice Logic app schedule to start our cluster(s) in a specified resource group. It will send a teams message like this so that ops are sure that everyone knows
Now, to have the same logic app for the stop
action, we just have to reproduce the same logic app and change the Invoke resource operation
.
Or, the lazy way, to copy paste the content of the code view in a new logic app.
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {...},
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {...}
},
"parameters": {
"$connections": {
"value": {
"arm": {
"connectionId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$rgname/providers/Microsoft.Web/connections/arm",
"connectionName": "arm",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/arm"
},
"office365": {
"connectionId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$rgname/providers/Microsoft.Web/connections/office365",
"connectionName": "office365",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/office365"
},
"teams": {
"connectionId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$rgname/providers/Microsoft.Web/connections/teams-2",
"connectionName": "teams-2",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/teams"
}
}
}
}
}
Just beware, since we are using managed identity, to activate the feature before the copy paste.
2. Automate AKS Certificate rotation
So now we have a logic app to automate start and stop of AKS cluster.
another action that we would like to automate is the rotateCertificates
action.
As mentioned in the documentation, the renewal of the certificate before the expiration is a new feature in GA, and rolled out until february.
However, we may need to trigger the renewal manually.
On the logic app part, we will again use an Invoke resource operation
and specify the rotateCertificates
operation.
This time, since this action is not without impact, a validation could be nice.
Logic apps come with an Outlook action which answer to this:
Depending on the answer, we will have to implement a different path in the logic app. We will do that with a Condition
control action:
Note the condition in code view:
"expression": {
"and": [
{
"equals": [
"@body('Send_approval_email')?['SelectedOption']",
"Approve"
]
}
]
}
When the workflow is triggered, we get a nice email such as this one:
Which we approve or rject to complete the workflow:
And that’s it ^^
3. Take away
In this 2 part article, we were able to create logic apps to manage Ops activities on AKS cluster. SO we have two main area of benefits here. The first one is obviosly on the AKS operations, for which we can rely on the Azure Resource Manager actions available in Logic App designer. The second is whazt we found out while designing those logic apps. We do have the Azure Resource Manager actions, and using the Data Operations and the Control actions, we can leverage an automated workflow, on any Azure resources, without managing password thanks to the support for Managed Identity.
To conclude, another interesting operations on AKS is the runCommand
. I will probably have a look in the near futur. On that, see you soon.