<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brian Flynn</title>
    <description>The latest articles on DEV Community by Brian Flynn (@bfardanis).</description>
    <link>https://dev.to/bfardanis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F466305%2Fdfe1d519-ff55-4d59-bab3-a0c9ffa740c1.png</url>
      <title>DEV Community: Brian Flynn</title>
      <link>https://dev.to/bfardanis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bfardanis"/>
    <language>en</language>
    <item>
      <title>CI/CD for Infrastructure</title>
      <dc:creator>Brian Flynn</dc:creator>
      <pubDate>Thu, 10 Sep 2020 10:02:30 +0000</pubDate>
      <link>https://dev.to/ardanis/ci-cd-for-infrastructure-k00</link>
      <guid>https://dev.to/ardanis/ci-cd-for-infrastructure-k00</guid>
      <description>&lt;p&gt;CI/CD (Continuous Integration, Continuous Delivery) is the practice of frequently deploying software to environments using automated deployments. Organisations use CI/CD to deploy application updates but they can also use CI/CD to deploy infrastructure updates.  &lt;/p&gt;

&lt;p&gt;In this post, we will use Terraform and Azure DevOps to build a CI/CD pipeline which will deploy infrastructure updates to environments hosted in Azure.&lt;/p&gt;

&lt;h1&gt;
  
  
  Terraform
&lt;/h1&gt;

&lt;p&gt;Terraform is an infrastructure as code tool. We can use Terraform to create, change and destroy infrastructure resources. Terraform uses YAML files to define resources. We can check these files into source control and use code review tools to review, share and collaborate on changes before infrastructure is updated. &lt;/p&gt;

&lt;p&gt;This post assumes the reader has some basic knowledge of Terraform, including understanding how to plan and apply changes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Azure DevOps
&lt;/h1&gt;

&lt;p&gt;Azure DevOps is a set of services development teams can use to manage software development projects. We will use Azure Pipelines to manage our build and deployments.   &lt;/p&gt;

&lt;h1&gt;
  
  
  Azure Environments
&lt;/h1&gt;

&lt;p&gt;We will assume the infrastructure is hosting a simple web application which will consist of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A monolithic web application&lt;/li&gt;
&lt;li&gt;A backing PostgreSQL database&lt;/li&gt;
&lt;li&gt;A file store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use Azure PAAS services to host the application as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Services to host the web application&lt;/li&gt;
&lt;li&gt;Azure PostgreSQL to host the database&lt;/li&gt;
&lt;li&gt;Storage Accounts for file storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Environment Topology
&lt;/h1&gt;

&lt;p&gt;We will define two environments, one for test and one for production. The environment topology is similar in both environments but may vary in resource count and configuration. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wTv0EtvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w00tj13f9d74nnsqdkn0.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wTv0EtvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w00tj13f9d74nnsqdkn0.PNG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipeline Design
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Build Once
&lt;/h2&gt;

&lt;p&gt;When building a CI/CD pipeline it is advisable to build once and promote the result (a single build artefact or binary) through the pipeline stages (environments). This prevents problems that may arise when software is compiled or packaged multiple times allowing slight differences to be included in the artefacts. Deploying different software to different stages of a  pipeline may introduce inconsistencies. These inconsistencies may invalidate test that passed on previous stages.&lt;br&gt;&lt;br&gt;
Users will typically generate a separate Terraform build artefact (a plan) for each environment. &lt;/p&gt;

&lt;p&gt;Fortunately, we can use Terraform modules to partially overcome this issue. We will define application “stacks” in reusable modules. These stacks define all the infrastructure required for the application to run. We can then deploy the application infrastructure consistently to each environment and use environment-specific root modules to control environment-specific (SKU, tiers, etc) configurations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stages
&lt;/h2&gt;

&lt;p&gt;The pipeline will consist of three stages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UvcR8xeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mqr05kshiiw9g9tvb0x8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UvcR8xeP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mqr05kshiiw9g9tvb0x8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Build
&lt;/h3&gt;

&lt;p&gt;This stage will produce a build artefact that will contain a terraform plan for each environment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;This stage will apply the test plan to the test environment and execute any necessary tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prod
&lt;/h3&gt;

&lt;p&gt;This stage will apply the production plan to the production environment and execute any necessary tests.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Implementation
&lt;/h1&gt;

&lt;p&gt;This repository contains all code referenced in this post. As we will be interacting with Azure resources the Azure CLI should be installed locally. &lt;/p&gt;
&lt;h2&gt;
  
  
  Code the Infrastructure
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Global Variables
&lt;/h3&gt;

&lt;p&gt;We will use a module to declare global variables. &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;Terraform maintains an internal representation of infrastructure it is managing. This representation is persisted to a state file and is used each time terraform generates a plan. We will store this state file in an azure storage account and configure terraform to use this storage account. We will create a module to manage the creation of the backend storage account. &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Web App Stack
&lt;/h3&gt;

&lt;p&gt;We will create a module which declares all infrastructure components needed to host the web application. Each environment module references this module. &lt;/p&gt;

&lt;p&gt;Modules contain inputs, resources and outputs. Inputs are defined in inputs.tf as follows:  &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The resources are defined in the main.tf file as follows:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;A module can also define outputs which can be used by referencing terraform files.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Environments
&lt;/h3&gt;

&lt;p&gt;Next, we will create the environment modules, one for test and one for production. These are found in infra/azure/terraform/envs.&lt;/p&gt;

&lt;p&gt;As we do not want to store the Postgres DB credentials in our terraform files. We will declare two input variables for these. We will pass these variables via command-line arguments as the environment modules are root modules.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We declare the terraform block, azure backend and provider in main.ft:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We then declare the web-app module and any environment-specific resources in a separate file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Creating and Destroying Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create the backend
&lt;/h3&gt;

&lt;p&gt;The backend is initialised manually as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cd infra/azure/terraform/backend
az login
terraform init
terraform plan
terraform apply
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Create an Environment
&lt;/h3&gt;

&lt;p&gt;We can now create the environments. We need to initialise a root environment module, create a plan, apply the plan and then destroy the infrastructure. &lt;/p&gt;

&lt;p&gt;To create and then destroy the test environment, execute the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cd infra/azure/terraform/envs/test
&lt;/span&gt;&lt;span class="gp"&gt;terraform init -backend-config "access_key=&amp;lt;backend storage account key&amp;gt;&lt;/span&gt;”
&lt;span class="gp"&gt;terraform plan -out test.tfplan -var=azure_subscription_id=&amp;lt;sub id&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;postgres_admin_login&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;admin login&amp;gt; &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;postgres_admin_password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;admin login&amp;gt;
&lt;span class="go"&gt;terraform apply test.tfplan
&lt;/span&gt;&lt;span class="gp"&gt;terraform destroy -var=azure_subscription_id=&amp;lt;sub id&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;postgres_admin_login&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;admin login&amp;gt; &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;postgres_admin_password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;admin login&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Please note that all infrastructure created above will incur charges. &lt;/p&gt;
&lt;h2&gt;
  
  
  Create the Pipeline
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Azure Service Connections
&lt;/h3&gt;

&lt;p&gt;The pipeline needs to connect to Azure to manage Azure Resources. To enable this, we have to create an Azure Service Connection. See here for more details. &lt;/p&gt;
&lt;h3&gt;
  
  
  Pipeline Definition
&lt;/h3&gt;

&lt;p&gt;We can define Azure DevOps Pipelines in YAML. The pipeline has three stages, a build stage and two release stages. &lt;/p&gt;
&lt;h4&gt;
  
  
  Build Stage
&lt;/h4&gt;

&lt;p&gt;This stage will consist of a single job which executes the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install terraform&lt;/li&gt;
&lt;li&gt;Initialise the test environment module&lt;/li&gt;
&lt;li&gt;Create the test environment plan&lt;/li&gt;
&lt;li&gt;Initialise production environment module&lt;/li&gt;
&lt;li&gt;Create the production environment plan&lt;/li&gt;
&lt;li&gt;Create build artefact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The build artefact contains the test and production environment plans. Azure DevOps will trigger the release pipeline when the build pipeline successfully creates a build artefact.&lt;/p&gt;
&lt;h4&gt;
  
  
  Test Stage
&lt;/h4&gt;

&lt;p&gt;This stage will apply the test plan to the test environment. It consists of a single deployment job which will execute the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download build artifact&lt;/li&gt;
&lt;li&gt;Install terraform&lt;/li&gt;
&lt;li&gt;Apply test plan&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Prod Stage
&lt;/h4&gt;

&lt;p&gt;This stage will apply the production plan to the production environment and will require approval to execute. It also consists of a single deployment job which will execute the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download build artifact&lt;/li&gt;
&lt;li&gt;Install terraform&lt;/li&gt;
&lt;li&gt;Apply test plan&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h3&gt;
  
  
  Environments
&lt;/h3&gt;

&lt;p&gt;We will create two environments in Azure DevOps pipelines and will configure the production environment to require approvals. &lt;/p&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;The pipeline requires the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend-access-key: The backend storage account access key&lt;/li&gt;
&lt;li&gt;azure-subscription-id: The subscription in which we will create the resources&lt;/li&gt;
&lt;li&gt;test-admin-login/ test-admin-password: Credentials for the test PostgreSQL DB&lt;/li&gt;
&lt;li&gt;prod-admin-login/ prod-admin-password: Credentials for the prod PostgreSQL DB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pipeline Tests
&lt;/h3&gt;

&lt;p&gt;The release stages should also execute tests to ensure the application is operational after each update.  If a test fails then the pipeline should not promote the artefact to the next stage. &lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline Triggers
&lt;/h3&gt;

&lt;p&gt;Ideally, each commit should trigger the build pipeline. When the build pipeline successfully creates an artefact, it triggers the release pipeline, which will automatically execute the Test stage and deploy to the Test environment. This will keep the deltas between deployments to the Test environment small and will make it easier to debug errors when they occur. Deployment to the production environment could be automatic, scheduled or manual. &lt;/p&gt;

&lt;h3&gt;
  
  
  Plans are Diffs
&lt;/h3&gt;

&lt;p&gt;Terraform plans are diffs between the current state of an environment and the desired state. If multiple changes are queued for deployment and an older change is deployed before more recent changes, the more recent changes are invalidated. We should not deploy they more recent changes as they were generated using a now invalid state and we should create a new build which contains any pending changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jPLRX1Xu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nsaczoo7dgfyec0owiam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jPLRX1Xu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nsaczoo7dgfyec0owiam.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;As we have seen, we can use infrastructure as code tools such as Terraform to automate infrastructure deployments. We have used Terraform, Azure DevOps and Azure in this example but we can implement pipelines using other tools such as Ansible or Pulumi. We can also target other cloud providers as these tools support multiple clouds. &lt;/p&gt;

&lt;p&gt;Finally, creating CI/CD pipelines for our infrastructure has many benefits:&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;We can test most infrastructure changes in test environments before we apply them to production. We can also add functional and non-functional tests to the pipeline to check if the application is operational, secure and performant after each deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Gates
&lt;/h2&gt;

&lt;p&gt;We can use quality gates to prevent invalid updates deploying to production. If a test stage fails to deploy or additional tests fail the production environment will not change. We will create a new build to address the errors encountered in the test environment. &lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Visibility and Audit Trails
&lt;/h2&gt;

&lt;p&gt;The pipeline acts as a single source of truth and we can use it to understand the state of our environments. We can also easily identify the commit (or commits) that produced a build artefact which can help with debugging and error triage. Finally, most build and release automation systems contain historical logs and change audit trails. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fewer Errors and Repeatable Results
&lt;/h2&gt;

&lt;p&gt;The pipeline automates all tasks involved in deploying infrastructure updates. This reduces errors that occur during manual deployments and ensures deployments are executed consistently. &lt;/p&gt;

&lt;h2&gt;
  
  
  It’s all Code
&lt;/h2&gt;

&lt;p&gt;If we can implement the pipeline in code (normally YAML) then we can use software development techniques to improve code quality:&lt;br&gt;
• Revision code using a source control system&lt;br&gt;
• Pull requests and code reviews ensure changes are valid&lt;br&gt;
• Pair programming improves code quality&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>terraform</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
