Azure Answered – 15 design considerations for apps destined for Microsoft Azure
Over the last 12 months, we’ve been meeting customers on the journey to Microsoft Azure. In the majority of cases, stage 1 of the journey is ‘lift and shift’ and application transformation is embarked upon in a later phase. The journey need not wait.
Whilst a lift and shift into Virtual Machines lands you in Microsoft Azure (a great first step!), the time will arrive when you’re going to want to make use of Azure’s PaaS and SaaS services to take the operational pain out of managing and deploying your applications. There are things a team can do to accelerate the journey.
Here are 15 curated tips I’ve learned on our own journey to public cloud.
1. Version all code in a Git repository and make use of git’s powerful features
Develop and exercise git discipline when it comes to managing your code. With a userbase of 50 million, GitHub provides a fantastic platform for git hosting and pleasantly integrates with Azure DevOps. There are opinionated git workflow CLI and UI tools that can either be used as a reference for developing a SCM strategy, an example being git flow. By developing a strategy involving feature, bugfix, release branches and tags, automated build & deployment pipelines will slot in easier.
Consider what’s being stored in your git repositories:
- Each application component/dependency should live in its own git repository
- Skeleton configuration only, inject config at deploy or runtime
- No secrets (use KeyVault)
- Store dependencies and other artifacts in Azure Artifacts and leverage as part of adjacent/child builds
- Version your database, implement migrations
- No logs or compilation output
- No personal user config files, no references to local developer paths
Also, do the following:
- Leverage .gitignore to your full advantage and make it difficult for developers to commit confidential/sensitive/personal information to an upstream repository.
- Your team has a code review process built in to ensure good code hygiene, test coverage and no accidental configuration slip ups (easy to do in a multi-tenant development environment).
- Choose between public and private repos wisely.
- Script database migrations and execute as part of each deployment.
Troy Hunt has a great blog describing the 10 commandments of good source control.
2. Leverage a CI/CD platform to build your code and infrastructure
Azure DevOps provides outstanding capability for constructing pipelines, building and releasing apps for codebases in almost any language. It also has a vibrant marketplace offering integration and reporting, everything from: JMeter, Postman Reporting, NodeJS script runners, Apple App Store publishers and much more.
When designing a pipeline for your application components, ensure you define actions for build, testing (unit, integration, security), release and run/deploy stages.
If you’ve never built a pipeline, start with a small, real application and get it working.
Azure DevOps provides predefined Build, Pipeline, Agent and System variables that make it easy to programatically drive your DevOps pipelines. For example you might decide to inject the Build.BuildNumber into a text file into a release built artifact.
Regardless of how your application is deployed (VMs, PaaS), the ideal minimal workflow is that a commit to your selected branch drives the execution of one or more pipelines to continuously deploy the application component.
3. Leverage Azure Artifacts to publish and consume packages / libraries / modules
Most application development teams have wasted hours setting up their own artifact repository for the storage of libraries, artifacts and other binaries. NuGet for .NET, Artifactory for Maven builds with Java, PyPI for python, the list goes on.
By leveraging a Azure Artifacts, a SaaS offering, you’ll offload the responsibility of managing those services to Microsoft and reap the benefits of integration with your Azure pipelines, alleviating your DevOps/Infrastructure teams of managing unnecessary infrastructure.
When executing build pipelines ensure each of your applications/libraries/modules/micro or macro services are pushed into Azure Artifacts. The artifacts can then be shared privately or publicly via ‘feeds’.
4. Manage and structure app config files wisely
Configuration is at the heart of every application. Config values drive application runtime behaviour.
The problem with configuration is that you’re only as strong as the weakest link. Across the lifetime of a git repo, at least one contributor will either unknowingly or purposely commit sensitive or confidential information to an upstream repo. Security and privacy is everyone’s responsibility … some upfront work and diligent pull request reviews will lower the risk.
Developing a configuration convention is critical. Pre/post git hooks and .gitignore can help prevent mistakes.
5. Inject configuration just in time
Configuration should always be injected as close to runtime as possible through environment variables or secrets, or through code execution.
6. Make use of Azure Key Vault
Azure Key Vault provides the ability to programmatically retrieve and manage certificates, keys and secrets. Key Vault also integrates with Azure DevOps via Variable groups making the job of building and publishing a breeze. Store and consume sensitive data like connection strings, secrets, API keys and leveraging them as part of your CI/CD pipeline.
To meet stricter compliance requirements, hardware security modules (HSMs) can be leveraged to ensure you bring and manage key vault with your own keys meeting FIPS 140-2 compliance requirements.
7. Treat backing services as attached resources
By treating each component/library/service as it’s own entity with dependencies on backing services, it should be possible to make those backing services hot swappable through a simple change in configuration.
It should be possible to point an application at a local SQL instance and later point to Azure SQL Database in staging/production.
8. Infrastructure as Code (IaC)
Application developers are deliberately lazy. For all intents and purposes, maximising a developer’s time is critical.
High functioning, productive application development teams can deploy and redeploy an application at the drop of a hat.
There are so many deployment platform options available to developers in Microsoft Azure: VMs, App Services, Azure Container Instances, Kubernetes, Functions. The choice of deployment platform has an impact on the agility and performance of your app dev teams.
Ideally any service developed by your organisation should be self-deployable to Azure and it shouldn’t require a Neuroscience major to figure out how to do it.
Microsoft’s ARM templates provide a way to define deployment parameters and Azure infrastructure and sit beside your code. Microsoft Azure permits the export of infrastructure to ARM templates to give you a quick start to building templates.
Ansible playbooks, Terraform code and Chef cookbooks can help you programmatically define infrastructure alongside your application sourcecode, forming the basis of your application deployment pipeline.
Regardless of the target cloud service and IaC approach, you should consider how you make deployments idempotent. I.e. an action should be able to be applied multiple times without changing the result beyond the initial run.
Your IaC implementation should ensure you can roll forward or backwards with a release, deploy a targeted release.
9. Stateless processes
Services should be deployed as stateless entities and should share nothing. This rule should be applied regardless of the deployment platform providing maximum compute portability and flexibility in the future.
Always store data in a relational (Azure SQL Database, SQL MI or SQL on VM) or unstructured databases (Azure Cosmos DB). Consider how you leverage temporary data stores such as Azure Cache for Redis.
Store files in blob storage, geo-replicated if required cross-region.
10. Compile and compress assets and publish them to a CDN
While this tip isn’t specific to Azure, you should always seek to push static assets into a CDN such as Azure CDN to reduce latency, bandwidth and load on your application servers increasing front-end responsiveness in this long-term age of javascript apps. CDNs place static assets as close as possible to the user’s HTTP request.
11. Build for scale-out / concurrency
Taking a stateless process design approach, this is made especially easy. Adding more compute instances to your application should be a single API or portal transaction away. The task of adding more concurrency (hosted by a VM, container) needs to be simple and reliable. Leverage Azure Automation accounts to drive actions under certain Azure monitor conditions to drive smarter application operations.
12. Opt for disposability
The app should be startable or stoppable immediately. Keep startup time to a minimum. If a process dies unexpectedly ensure it’s respawned automatically.
13. Environment parity
Test, pre-production, production and disaster recovery application environments should look, smell and taste the same. Given an operator with the correct rights, it should be easy to repeatably switch between environments due to a configuration driven approach to deployment.
- Leverage subscription and resource groups to your advantage. Keep environments separate in a way that makes logical sense.
- Keep environments at parity (including baseline application config).
- Use different backing services per environment, driven by configuration stored in KeyVault. Forcing different environments into separate subscriptions with different IAM controls through Azure Active Directory can help enforce this.
14. Employ critical Azure tools to monitor and ensure reliability
Microsoft offer a plethora of operational insights for your deployed infrastructure and applications. Leverage Azure App insights and Azure Monitor for runtime insights into your infrastructure and application and Log Analytics for centralised aggregation of your logs.
Ensure you have a thorough application logging strategy in place using a commonplace native or 3rd party logging framework and ensure all logs go to log rotated files, but preferably to STDOUT/ERR for maximum portability to containers in the future.
15. Ensure all admin tasks are one off-processes
All admin tasks should always be run as repeatable tasks and documented in your README.md file.
Popular admin tasks are changing database schemas (running migrations), fixing data consistency, optimising database indicies or archiving / changing the storage tier of long-lived files in blob storage.
Run common one-off processes as part of a deployment/release pipeline. These processes should be idempotent.
Are you on the journey to Microsoft Azure? With our Azure Managed Services, we’ll provide you with the infrastructure and platform support you need to keep your applications singing.