Skip to main content

Container Apps

Container Apps replaced Web Apps in Release 1.14.0.

The change is purely architectural and no functional changes were done. Container Apps are deployed within a Managed Environment. Managed environment has defined Workload profiles, we're using currently Consumption.

Once Container Apps will be deployed on higher environments we'll be able to define amount of resources necessary per application and their sizing + scaling.

It is possible to pre-buy some CPUs and memory per 1 or 3 years and have up to 17% of savings, but that is also something to be verified in the future. Within the Managed Environment are defined as well Dapr Components used by Container Apps.

Dapr integration

Applications that use Dapr have adjusted code to be able to use embedded in Container App resource Dapr integration. The difference is that right now apps are using Dapr API, instead of SDK. In different words, Dapr is running as a separate container instead of running as a sidecar container.

Resources

Currently each app is within resources configuration, which means, they are set on 0.5CPU and 1Gi memory. Once they are deployed on higher environments, we'll need to optimize container sizes and define scaling rules, most likely per CPU/memory usage.

CI/CD

Pipelines are built in a way to do all the necessary tasks at one run. It means:

  1. building a docker image + pushing it to ACR (with name of the <application-name>:latest)
  2. pulling Falcon image and merging it with our image and pushing to ACR with name <application-name>-protected:version -> version is incremented by 1 on each build -> 1.0.x
  3. Container App update -> pointing the container configuration in ACA to the new container in the repository, causing deployment of a new revision.

Each pipelines are named as:

  • build-deploy-protect-push-'app-name'.yml
  • each of this pipeline is using separate template using unique Docker build command for each app: tmpl-build-protect-push-'app-name'.yml
  • each of this template is using the same one template to fullfil the rest of the steps: tmpl-protect-update-container.yml

Pulumi

The solution is not perfect, and there are some issues or things to remember with Pulumi. To enable auto-deployment of new version of the Container App, there's the versioning of new images enabled (1.0.x). Therefore the image name (or its tag) is a dynamic thing and normally in Pulumi this value is constant, therefore, we have to dynamically pull the currently set value, in order to not use outdated images on each run of Pulumi. This piece of logic happens on top of each Container Apps definition:

currentContainerLuzmoPostgresPluginImageTag = pulumi.all([rgName]).apply(([rgName]) => {
return app.getContainerApp({
containerAppName: "aca-luzmo-plugin-postgres",
resourceGroupName: rgName,
});
}).template?.apply(t => {
if (t?.containers) {
return t.containers[0].image;
} else {
return "";
}
});

The function getContainerApp is used, the issue with it, is that it will crash and fail, is the ContainerApp does not exist. Therefore before the first deployment of the Container Apps, this piece of code should be commented out:

let currentContainerLuzmoPostgresPluginImageTag; 

// currentContainerLuzmoPostgresPluginImageTag = pulumi.all([rgName]).apply(([rgName]) => {
// return app.getContainerApp({
// containerAppName: "aca-luzmo-plugin-postgres",
// resourceGroupName: rgName,
// });
// }).template?.apply(t => {
// if (t?.containers) {
// return t.containers[0].image;
// } else {
// return "";
// }
// });

const luzmoPostgresPluginImage = currentContainerLuzmoPostgresPluginI....

Postgres Connection

We're allowed to connect to Postgres Database only by using Entra ID authentication. In order to make it work:

  • the Identity assigned to the Container App has to be linked to a database role:
select * from pgaadauth_create_principal_with_oid('<database_user>', '$objectId', 'service', false, false);
  • In the pipeline template it's done in a way to not fail, if the role already exists (but also not to create the duplicates), you can check tmpl-entra-id-postgres-access.yml

  • Once user is created, we have to grant privileges:

GRANT pg_read_all_data TO <database_user>;
GRANT pg_write_all_data TO <database_user>; 

The database setup is done!

The database connection string should contain \<database_user> as username and Entra ID token as the password.

Resources and Scaling

All Container Apps currently have set the default amount of resources: 0.5 CPU and 1Gi of memory (except docs-internal)

resources: {
cpu: 0.5,
memory: "1Gi",
}

Default value was set, as currently none of the Apps is reaching limits of assigned resources.

All Container Apps currently have set scaling rule min replicas 1, max replicas 3, if CPU utilization reaches 75%

scale: {
maxReplicas: 3,
minReplicas: 1,
rules: [{
name: "cpu-scaling-rule",
custom: {
type: "cpu",
metadata: {
type: "Utilization",
value: "75"
}
}
}]
}

Scaling won't be really used, as the load is too low, but in case of surge messages (e.g.: a device that sends lots of data, buffered many messages and the sent all of them at once), scaling should ensure operational continuity