Eduard Keilholz

Hi, my name is Eduard Keilholz. I'm a Microsoft developer working at 4DotNet in The Netherlands. I like to speak at conferences about all and nothing, mostly Azure (or other cloud) related topics.
LinkedIn | Twitter | Mastodon | Bsky

I received the Microsoft MVP Award for Azure

Eduard Keilholz
HexMaster's Blog
Some thoughts about software development, cloud, azure, ASP.NET Core and maybe a little bit more...

Container Apps - With Pollstar (part 1)

Container Apps - With Pollstar

So I’ve been working with Container Apps in my spare time lately and now I’m confused. I’m a software developer mostly using Microsoft technologies. Dotnet (or .NET if you like), Visual Studio (Proper and Code), C#, and the Azure cloud. And with this combination, why in the world would we need containers? Azure has this Web App resource, you can almost literally throw anything at it and it will run. But Hey, I see the investments Microsoft is doing in Containers, and I do realize that our digital world is moving more and more towards this container solution. Time for me to dig in and not miss this boat. So that’s where this container investigation came from.

This is part one of a multi-part blog. In this part, I will explain the idea of the system that I created, and show how the plumbing for the Azure Infrastructure is done.

Although this investigation part was fun, ideas really start to shine when you bring a demo into the mix. The idea was to be able to host polls, that a live audience can view and vote on. Or maybe even better, a couple of polls. So I thought a little bit about this idea and came up with a solution that I call Pollstar from now on.

It is important to understand that the original idea for this app, was to create a solution hosted in Azure with Azure Container Apps. Parts of this solution can easily be marked as overengineered to service this goal.

The Polls service

The polls service is a service used to create, update and activate a Poll. A Poll is basically a question, combined with two or more multiple choice answer options. Because of possible typos, there must be the ability to update a Poll. And because more polls can be created, there must be the ability to activate a poll.

The Sessions service

Because more Polls can be created, I needed some sort of a grouping mechanism. This grouping mechanism is called a Session. So a session is a shell that holds zero or more polls, of which one can be marked as active at a time. To maintain sessions, I created a Sessions service.

The Users service

Once a poll is presented to an individual, this individual sees a question. And with that question, a couple of buttons depending on the amount answer options configured for that Poll. The system must be able to identify individuals hitting the buttons to prevent them from voting multiple times. The Users services identifies each individual.

The Votes service

Finally, there is the Votes service. You guessed it, this service is responsible for receiving and processing Votes cast by users. This Votes service is (at the time of writing) the one I’m least proud of because of its implementation, but there is always room for improvement. Now all the moving parts are identified, let’s dig a little bit into the hosting solution. Azure Container Apps will, under the hood, control a Kubernetes cluster. So that is, next to AKS (Azure Kubernetes Service) a second managed service that you can take advantage of to host a container environment. This means that like any other container solution, you need a secluded area where your containers run. For Container Apps, this is called a Container Apps Environment.

To provision this environment, I use Infrastructure as Code (Bicep). Because this environment has a completely different life cycle and most of all, the environment must be present before I can deploy the first container app, I decided to create a new repository, with an independent CI/CD mechanism to deploy the… let’s call it ‘base infrastructure.

The base infrastructure resources

The figure above shows all the resources that this base infrastructure contains. Application Insights and Log Analytics for log ingestion and tracking and tracing problems. Then Azure App Configuration so I only have to add configuration values to only one resource, and make all configurable components get their config from this service. The Container Apps Environment is mentioned in the paragraphs above. Azure Web Pubsub (read my beginner and advanced scenarios blogs about PubSub) for real-time communication. And finally an Azure Container Registry. This is sort of a library that can hold containers (with different versions). Once a Container App is deployed, it needs to pull a container from a certain place, and the Container Registry can be one of these places.

Describing the infra

The infrastructure is fully written in Bicep. Bicep is a domain-specific language very powerful for describing cloud resources for Azure. Together with Henry Been and Erwin Staal, I wrote a book on Infrastructure as Code for Azure. If you’re not familiar with this concept, review this book and learn all the ins and outs you need to create full enterprise-scale cloud architectures with code.

There are two bicep files, main.bicep and resources.bicep. The main.bicep file, targets the Subscription scope. This is to be able to create a new Resource Group that will contain all the resources for my base infra. The main.bicep will call the resources.bicep file as a module. This will result in a new deployment in Azure, allowing to switch the scope. The scope of the resources.bicep deployment will be set to the Resource Group that has just been created in main.bicep.

resource integrationResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: integrationResourceGroupName
  location: location

module integrationModule 'integration.bicep' = {
  name: 'IntegrationModule'
  scope: integrationResourceGroup
  params: {
    environmentName: environmentName
    locationAbbreviation: locationAbbreviation
    systemName: systemName
    location: location
    availabilityRegions: availabilityRegions
    availabilityEndpoints: availabilityEndpoints

The snippet above is part of the main.bicep file. You can see that a resource group is created in the top part, and the resources.bicep file is referenced as a module while changing the scope to the just-created resource group.

Getting CI/CD up and running

I host this entire project on GitHub, which has a mechanism called GitHub Actions. With GitHub Actions, you can hook into certain actions that take place in your source control system. I think one of the most common hooks is a ‘push’. This means that new code (or code changes) was detected. So in this example, you can perform an action when the code in your source control system has changed. This is exactly what I’m looking for. GitHub Actions supports a long list of events that you can use to trigger actions, for me in this case, the push is just fine.

In this blog I tell a little bit more about GitHub actions. If you want to know it all, I recommend you buy this book written by… Oh, me! ;)

So this action consists of three jobs. One is to transpile my Bicep files to an Azure native ARM Template (JSON file). Then I publish this JSON file together with the parameter files it needs as workflow artifacts.

    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Build & Push
        working-directory: infrastructure
        run: |
          az bicep build --file main.bicep
          az bicep build --file integration.bicep          
      - name: Publish Artifact
        uses: actions/upload-artifact@v2
          name: bicep-templates
          path: infrastructure/*.json

Doing this transpile step is far from mandatory, you could also easily deploy infrastructure from a Bicep file. I think that this transpile step is some sort of a test to see if the Bicep files are valid, and having this job also makes room for testing the infrastructure with Pester for example in the future.

The following two jobs are quite similar. They both deploy the infrastructure on Azure but use a different subscription and a different parameters file. This is to host a test, and production environment.

  needs: [publish-bicep]
  runs-on: ubuntu-latest
    - name: Download Artifact
      uses: actions/download-artifact@v2
        name: bicep-templates
        path: ./infrastructure

    - name: Azure Login
      uses: azure/login@v1.4.0
        creds: $/{/{ secrets.AZURE_TEST /}/}

    - name: Deploy Infrastructure
      id: arm
      uses: Azure/cli@1.0.4
        inlineScript: az deployment sub create --name pollstar-integration  --location northeurope --template-file ./infrastructure/main.json --parameters ./infrastructure/params.test.json

The snippet above shows the deployment to the test environment. You can see that I use the Azure Login task to log in to Azure. The credentials used, come from a secret in my repository. The value of the secret looks like so:

  "clientId": "00000000-0000-0000-0000-000000000000",
  "clientSecret": "super-secret-string",
  "subscriptionId": "00000000-0000-0000-0000-000000000000",
  "tenantId": "00000000-0000-0000-0000-000000000000"

The tenantId value should be the ID of your tenant, and the subscriptionId the ID of the subscription you want to deploy to. Then in Active Directory, you need to create an App Registration. Then generate a secret for that App Registration and copy the Client Id (App ID) and the value of the secret over to the JSON above.

If you have the Azure CLI installed, you can log in to Azure using the az login command. And then execute the following command to create the App Registration above, with a secret:

az ad sp create-for-rbac --name "myApp" --role owner --scopes /subscriptions/{subscription-id}

Warning – Note that this App Registration now has owner permissions on your target subscription. This is needed for this deployment, but you may want to review the permissions and tweak them a little bit here and there.

If you’re not comfortable with these kinds of CLI commands, you can also go to the Azure Portal and create an App Registration with its secret there. Don’t forget to also grant permissions to the App Registrations because else you cannot create a deployment.

This concludes part one of this multi-part blog. Follow me on Twitter or LinkedIn if you like to be notified when the next part of this blog is published.

The Pollstar app is heavily under construction so please bare with me if its broken or doesn’t perform once in a while, but you can review the product this demo has been taken offline