Building the Pipeline
The first part of the pipeline for installation and building is kept from the generated pipeline: install the latest node version, 16.14.0 is the latest stable version at the writing of this post; install the specific angular cli version globally needed for the application, 13.2.5; install packages from package.json file and install a library from a tar archive located in our repository. Finally, run ng build to create a dist folder of the angular application that can be deployed to the cloud.
steps: - task: NodeTool@0 inputs: versionSpec: '16.x' displayName: 'Install Node.js' - task: Npm@1 displayName: 'Angular CLI 13.2.5' inputs: command: custom verbose: false customCommand: 'install -g @email@example.com' - task: Npm@1 displayName: 'npm install' inputs: command: custom customCommand: 'install file:custom-angular-lib.tgz --legacy-peer-deps' - task: Npm@1 displayName: Build inputs: command: custom verbose: false customCommand: 'run build'
Next, we wanted to update our job with caching to improve the run time of the pipeline. We added the npm_config_cache variable and used a “Cache” task type to set up the npm install. Using an environment variable to a path under $(Pipeline.Workspace) ensures the cache is accessible from container and non-container jobs. The logic on the cache task attempts to restore the cache, if successful the npm clean install step will not need to run.
variables: npm_config_cache: $(Pipeline.Workspace)/.npm ... - task: Cache@2 inputs: key: 'npm | "$(Agent.OS)" | package-lock.json' restoreKeys: | npm | "$(Agent.OS)" path: $(npm_config_cache) displayName: Cache npm - script: npm clean install
Lastly, building the Angular project with the ng build command and publish the created dist folder to our AzDO artifacts for use in the next stage, deployment.
- task: Npm@1 displayName: Build inputs: command: custom customCommand: 'run build' - task: CopyPublishBuildArtifacts@1 displayName: 'Copy Publish Artifact: angular-web-app' inputs: CopyRoot: /dist Contents: '**' ArtifactName: angular-web-app ArtifactType: Container
TOP 10 REASONS TO CHOOSE YAML FOR YOUR NEXT AZURE DEVOPS CI/CD PIPELINE
Discover reasons to choose YAML for your next Azure DevOps Pipeline and the benefits of a unified development experience.
Deploying the Pipeline
We had two deployment ideas in mind: Azure Kubernetes Service, AKS, and Azure App Service. Deploying to App Service requires the archived build artifact and pushing it to the Azure Subscription. Alternatively, if we wanted to use our application in AKS the application is bundled into a Docker image.
Before either deployment option, a task to sign in to the Azure subscription is required. More information on setting up to deploy to Azure can be found in Microsoft’s documentation.
- task: AzureCLI@2 displayName: Az Login inputs: azureSubscription: <Name of the Azure Resource Manager service connection> scriptType: bash scriptLocation: inlineScript inlineScript: | echo "Login to AZ" az login --name <login name>
Deploy to App Service
The Azure Web Apps task can be used to deploy the Angular application artifact directly to the App Service. The task takes the artifact and pushes it to the App Service.
- task: AzureWebApp@1 displayName: 'Deploy Azure Web App : angular-app' inputs: azureSubscription: <Name of the Azure Resource Manager service connection> appName: angular-app appType: webAppLinux package: $(Pipeline.Workspace)/angular-web-app
Container for ACR and AKS
In order to deploy to an AKS cluster, the Angular application will need to be containerized and deployed to the Azure Container Registry, ACR. A basic Nginx container that hosts the dist folder is a common Angular dockerizing practice. Using the dockerfile in the repository, the pipeline will build the image and then push it to the ACR in our AZ subscription. Once the container is pushed to the registry, it can be used in any container service like AKS. See the Microsoft documentation on how to push to an Azure Container Registry.
- task: AzureCLI@2 displayName: Az Container Registry Login inputs: azureSubscription: <Name of the Azure Resource Manager service connection> scriptType: bash scriptLocation: inlineScript inlineScript: | echo "Login to AZ" az acr login --name <login name> - task: AzureCLI@2 displayName: Docker Build angular-app inputs: azureSubscription: <Name of the Azure Resource Manager service connection> scriptType: bash scriptLocation: inlineScript inlineScript: | echo "Build image with docker" docker build -t angular-app:latest -f Dockerfile . - task: AzureCLI@2 displayName: Docker Tag and Push angular-app inputs: azureSubscription: <Name of the Azure Resource Manager service connection> scriptType: bash scriptLocation: inlineScript inlineScript: | docker image tag angular-app:latest <name><endpoint>/<repo>/angular-app:latest docker image tag angular-app:latest <name><endpoint>/<repo>/angular-app:<build id> docker image push <name><endpoint>/<repo>/angular-app:<build id> docker image push <name><endpoint>/<repo>/angular-app:latest
Azure Artifacts vs. Caching
Caching was chosen as a solution over creating an artifact to improve build time by reusing files from earlier pipeline runs. The Microsoft documentation recommends using pipeline caching specifically for reducing build time and reusing files from previous runs. On the other hand, a pipeline artifact should be used if a job needs the files produced in a previous job in order to succeed. Since the installation of the node modules could be done even if a pulled cache fails, it made more sense to use caching.
By using AzDO pipelines, we were successful in creating a build pipeline with caching while having the flexibility to deploy to Azure App Service or AKS. The caching of the node modules sped up our installation task from 15 minutes to 3 minutes. The retrieval of the node modules still takes time but is faster overall, and possibly more cost-effective. Using the artifacts would not have benefited us in the same manner. Finally, the built-in Azure CLI tasks allow us to either deploy the Angular application directly to App Services or to build a Docker container, which allows us to push the image up to our Azure Registry for use in AKS or to use the container elsewhere.