Докеризация скриптов power shell


I previously wrote about
Automating maintenance tasks with Azure Functions and PowerShell.
That combo has been my go-to solution for many automation tasks.

However, there are scenarios where Azure Functions are not the best fit. For example, when you need to run a long-running task or need to have more control over the environment.
Azure Functions Premium plan
is typically the solution for these scenarios, but it’s more expensive than the Consumption plan.

There is currently a preview feature called
Azure Container Apps hosting of Azure Functions
which might be a good fit for these maintenance tasks also in the future.

But in this post, I’ll show alternative solution for running PowerShell tasks:
Container App Jobs.

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/job1.png

In this demo setup, I want to run my maintenance tasks with PowerShell scripts in a container app.

More precisely, I want to run a PowerShell script with generic image which has Azure PowerShell module installed
but the actual script is mounted from external storage.

Here is the architecture:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/architecture.png

The idea is to have my maintenance tasks written as normal PowerShell scripts and place them
into Azure Files. Then these scripts are mounted to the container app and executed.
And of course, I’ll use managed identity for identity for running the tasks:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/architecture2.png

To make it concrete, the PowerShell script can be as simple as this:

"This is example job script"

Before that script is executed, behind the scenes, Connect-AzAccount is executed with managed identity,
so you don’t need to worry about credentials in your maintenance scripts.

Create a job with Azure Container Apps

:/>  Hack Like a Pro: Windows CMD Remote Commands for the Aspiring Hacker, Part 1 « Null Byte :: WonderHowTo

Use storage mounts in Azure Container Apps

Tutorial: Create an Azure Files volume mount in Azure Container Apps

The above documentation uses mixture of using CLI tooling and then exporting container app to YAML
to be able to leverage the volume mounts.
I’m not big fan of this kind of approach, so I’ll show how to do this with YAML from the beginning.

As always, the full code is available in my GitHub repository (deploy.ps1 has all the details),
but the main parts of the deployment in this post:

First, you need to create the container apps environment:

# Create Container Apps environment

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/share1.png

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/share2.png

You can now deploy your script files to the storage using Azure CLI:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/explorer1.png

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/explorer2.png

Next, we’ll add the storage to the container app environment:

# - Add storage to the environment

Finally, I’m ready to deploy the container app job from YAML:

properties: workloadProfileName: Consumption environmentId:  configuration: replicaRetryLimit: 0 replicaTimeout: 1800 triggerType: Schedule scheduleTriggerConfig: cronExpression: 0 12 * * * parallelism: 1 replicaCompletionCount: 1 template: containers: - env: - name: AZURE_CLIENT_ID value: 

If you look carefully, then you’ll notice couple of things:

  • jannemattila/azure-powershell-job image is used to run this job
  • AZURE_CLIENT_ID environment variable is used to pass the managed identity client ID to the container
  • SCRIPT_FILE environment variable is used to point to the script file
  • volumes and volumeMounts are used to mount the storage account to the container
  • cronExpressionis used to define the interval of the job

So what is in the jannemattila/azure-powershell-job image?

It’s a super simple image with Azure PowerShell module installed
and wrapper script to start your actual script file.
You can find the source code from my GitHub repository:

:/>  Создание reg-файлов. Синтаксис редактирования записей реестра

And the image is available in Docker Hub:

After the above deployment, I can see the job in the portal:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/job1.png

And it does have the attached volume as well:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/job2.png

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/job3.png

I can start that job directly from portal:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/runnow.png

Similarly, I can start the job with Azure CLI:

After the job is started, I can see it the run history:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/runhistory.png

I can see the detailed logs of the run by clicking the Console:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/logs0.png

I can then use my KQL skills to just show the relevant fields:

 

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/logs1.png

Some benefits of this approach:

  • Full control of the used software versions (as you can see from the above logs)
    • E.g., PowerShell 7.4 now (and not when it’s GA as in Azure Functions)
  • VNET support
  • By separating the script from the container, you can:
    • Focus on the PowerShell script development
  • From the infrastructure deployment perspective:
    • Might be easier to deploy to mounted storage account than in some other solutions

Here is my cost analysis view for that resource group:

/assets/posts/2024/05/20/automating-powershell-tasks-with-container-apps/costs.png

From that $4.52 cost, container registry has taken $4.42 since I was using
Basic
tier for that registry.

If you think about the portability of this solution, then please read my post
Arc-enabled Kubernetes and Microsoft Entra Workload ID.
It shows how you can take this solution to elsewhere
and still use the managed identity for running the scripts.

To show what I mean, here is the same job in self-hosted Kubernetes:

 Write-Output "This is example run.ps1 (from configmap)" 
 # No need to set this manually,  # since workload identity will automatically set it # - name: AZURE_CLIENT_ID 

In the above YAML, I’m using Workload ID to pass the managed identity to the container.

:/>  Как узнать IP адрес по заданному имени хоста? )

Here is the output from the job:

kubectl logs Azure PowerShell Job
https://github.com/JanneMattila/azure-powershell-job
https://hub.docker.com/r/jannemattila/azure-powershell-job
Image: 1.0.5
PowerShell 7.4.2
.NET 8.0.4
Az 11.5.0
Job parameters:
AZURE_CLIENT_ID: edad5241-56ba-4fea-91c5-c0e1d6149e39
SCRIPT_FILE: /mnt/run.ps1abbreviatedRunning script: /mnt/run.ps1
This is example run.ps1 (from configmap)

The above demo shows how you can use the same script and same approach in different environments.
That can be Azure Container App Job or then your own self-hosted and Arc-enabled Kubernetes.

Conclusion

Okay, I admit, that to the people who are not so familiar with containers,
this might feel complex solution. But I think in many scenarios,
you can split the infrastructure work and script development work to different people
and you can find good balance between the two.

I hope you find this useful!