Benefits of Continuous Delivery (CD)

  • Post comments:0 Comments

In our previous post, we explored Continuous Integration (CI) and how it helps teams merge, build, and test code changes efficiently. But what happens after the code is integrated? This is where Continuous Delivery (CD) comes into play.

The benefits of Continuous Delivery extend beyond integration, ensuring that software is always in a deployable state. With CD, teams can automate release processes, minimize manual interventions, and accelerate software delivery. Let’s dive into what Continuous Delivery is and why it matters in DevOps.

πŸ“Œ Missed our previous post? Read The Benefits of Continuous Integration to see how CI fits into the DevOps workflow.

What is Continuous Delivery (CD)?

Continuous Delivery (CD) is a DevOps practice that ensures software can be released to production at any time with minimal effort. It extends Continuous Integration by automating the testing, deployment, and release processes, ensuring that new features and bug fixes reach users faster and more reliably.

While Continuous Deployment (a related concept) automates releases directly into production, Continuous Delivery gives teams the control to release software when they choose, making it a balance between automation and governance.

Key Benefits of Continuous Delivery

The benefits of Continuous Delivery go beyond automationβ€”it transforms how teams build, test, and deploy applications, improving overall efficiency.

πŸš€ Faster and More Reliable Releases
  • Automated pipelines remove manual bottlenecks, allowing teams to deploy frequently and with confidence.
  • Faster feedback loops ensure that defects are caught before they reach production.
πŸ”„ Improved Software Quality
  • Automated testing verifies that every change meets quality and security standards before deployment.
  • Teams can deliver stable, well-tested applications with fewer post-release issues.
πŸ’‘ Reduced Deployment Risks
  • Small, incremental updates are easier to test and rollback compared to large, complex deployments.
  • CD ensures that every release is low-risk and highly controlled.
πŸ”§ Increased Developer Productivity
  • Developers focus on writing code rather than manual deployment tasks.
  • Automated pipelines allow teams to experiment and innovate without fear of breaking production.
🌍 Better Collaboration Across Teams
  • CD fosters a culture of shared responsibility among developers, testers, and operations teams.
  • Transparency across the pipeline ensures everyone is aligned on release cycles and readiness.

How to Implement Continuous Delivery

Getting started with Continuous Delivery requires a well-structured pipeline and automation strategy. Here’s how:

1. Extend Continuous Integration (CI) with Automated Deployment Pipelines πŸ“¦
  • Use tools like Jenkins, GitHub Actions, GitLab CI/CD, or AWS CodePipeline to automate builds and deployments.
2. Automate Testing at Every Stage βœ…
  • Implement unit tests, integration tests, security scans, and performance testing to ensure release readiness.
3. Use Infrastructure as Code (IaC) πŸ—οΈ
  • Tools like Terraform, Ansible, or AWS CloudFormation help automate and standardize environments.
4. Adopt Feature Flags for Safer Releases πŸš€
  • Feature toggles allow teams to deploy code without enabling it for all users immediately.
5. Monitor and Gather Feedback Continuously πŸ“Š
  • Use observability tools like Prometheus, Datadog, or AWS CloudWatch to track system performance.

πŸ“Œ Example: A GitHub Actions workflow automating the deployment process of static front end code.

name: Build and Deploy Static Website to S3

on:
  push:
    branches:
      - prod
    paths:
      - "frontend-react-js/**"  # Trigger only when files in this folder change

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set Up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18  # Adjust based on your project

      - name: Install Dependencies
        run: |
          cd frontend-react-js
          npm install

      - name: Disable CI checks
        run: echo "CI=false" >> $GITHUB_ENV

      - name: Build the Website
        env:
          REACT_APP_BACKEND_URL: "<api-endpoint>"
          REACT_APP_FRONTEND_URL: "<frontend-endpoint>"
          REACT_APP_AWS_PROJECT_REGION: ${{ secrets.AWS_REGION }}
          REACT_APP_AWS_COGNITO_REGION: ${{ secrets.AWS_REGION }}
          REACT_APP_AWS_USER_POOLS_ID: ${{ secrets.AWS_USER_POOL_ID }}
          REACT_APP_CLIENT_ID: ${{ secrets.AWS_USER_POOL_APP_CLIENT_ID }}
          REACT_APP_API_GATEWAY_ENDPOINT_URL: ${{ secrets.API_GATEWAY_ENDPOINT_URL }}
        run: |
          cd frontend-react-js
          npm run build

      - name: Configure AWS CLI
        run: |
          aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws configure set region ${{ secrets.AWS_REGION }}

      - name: Deploy to S3
        run: |
          aws s3 sync frontend-react-js/build/ s3://${{ secrets.S3_BUCKET_NAME }}/ --delete

      - name: Invalidate CloudFront Cache
        run: |
          aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DIST_ID }} --paths "/*"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ${{ secrets.AWS_REGION }}

      - name: Delete Build Folder
        run: rm -rf frontend-react-js/build

πŸ“Œ Example: A CloudFormation template provisioning AWS CodePipeline and AWS CodeBuild that takes its sources from Github.

AWSTemplateFormatVersion: 2010-09-09
Description: |
  - CodeStart connection v2 to Github
  - CodePipeline
  - CodeBuild used for baking images for containers:
    - CodeBuild Project
    - CodeBuildRole

Parameters:
  GithubBranch:
    Type: String
    Default: prod
  GithubRepo:
    Type: String
    Default: "denisgulev/aws-bootcamp-cruddur-2023"
  ClusterStack:
    Type: String
  ServiceStack:
    Type: String
  S3Bucket:
    Type: String
  LogGroupPath:
    Type: String
    Default: "/cruddur/codebuild/bake-service"
    Description: The path to the log group for the build project
  LogStreamName:
    Type: String
    Default: "backend-flask"
    Description: The CodeBuild image to use for the build project
  CodeBuildImage:
    Type: String
    Default: aws/codebuild/amazonlinux-aarch64-standard:3.0
    Description: The CodeBuild image to use for the build project
  CodeBuildComputeType:
    Type: String
#    Default: BUILD_GENERAL1_SMALL
    Default: BUILD_GENERAL1_MEDIUM
  CodeBuildTimeoutMins:
    Type: Number
    Default: 20
    Description: The build project timeout in minutes
  BuildSpec:
    Type: String
    Default: "backend-flask/buildspec.yml"
    Description: The buildspec file to use for the build project

Resources:
  CodeBuildBakeImage:
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub "${AWS::StackName}-BakeImage"
      TimeoutInMinutes: !Ref CodeBuildTimeoutMins
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: !Ref CodeBuildComputeType
        Image: !Ref CodeBuildImage
        Type: ARM_CONTAINER
        PrivilegedMode: true
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Ref LogGroupPath
          StreamName: !Ref LogStreamName
          Status: ENABLED
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Ref BuildSpec
  CodeBuildRole:
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: [ codebuild.amazonaws.com ]
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-S3Policy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - s3:PutObject
                  - s3:GetObject
                Effect: Allow
                Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"
        - PolicyName: !Sub "${AWS::StackName}-ECRPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - ecr:BatchCheckLayerAvailability
                  - ecr:CompleteLayerUpload
                  - ecr:GetAuthorizationToken
                  - ecr:InitiateLayerUpload
                  - ecr:PutImage
                  - ecr:UploadLayerPart
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                Effect: Allow
                Resource: "*"
        - PolicyName: !Sub "${AWS::StackName}-VPCPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - ec2:CreateNetworkInterface
                  - ec2:DescribeDhcpOptions
                  - ec2:DescribeNetworkInterfaces
                  - ec2:DeleteNetworkInterface
                  - ec2:DescribeSubnets
                  - ec2:DescribeSecurityGroups
                  - ec2:DescribeVpcs
                Effect: Allow
                Resource: "*"
              - Action:
                  - ec2:CreateNetworkInterfacePermission
                Effect: Allow
                Resource: "*"
        - PolicyName: !Sub "${AWS::StackName}-LogsPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Effect: Allow
                Resource:
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupPath}*"
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupPath}:*"
  CodeStartConnection:
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html
    Type: AWS::CodeStarConnections::Connection
    Properties:
      ConnectionName: !Sub "${AWS::StackName}-Connection"
      ProviderType: GitHub
  Pipeline:
    # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-pipeline.html
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Type: S3
        Location: !Sub "${S3Bucket}"
      RoleArn: !GetAtt CodePipelineRole.Arn
      Stages:
        - Name: Source
          Actions:
            - Name: ApplicationSource
              RunOrder: 1
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: CodeStarSourceConnection
              OutputArtifacts:
                - Name: SourceOutput
              Configuration:
                ConnectionArn: !Ref CodeStartConnection
                FullRepositoryId: !Ref GithubRepo
                BranchName: !Ref GithubBranch
                OutputArtifactFormat: CODE_ZIP
        - Name: Build
          Actions:
            - Name: BuildContainerImage
              RunOrder: 1
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: ImageDefinition
              Configuration:
                ProjectName: !Ref CodeBuildBakeImage
                BatchEnabled: false
        - Name: Deploy
          Actions:
            - Name: Deploy
              RunOrder: 1
              ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: 1
                  Provider: ECS
              InputArtifacts:
                - Name: ImageDefinition
              Configuration:
                # In minutes
                DeploymentTimeout: "10"
                ClusterName:
                  Fn::ImportValue:
                    !Sub "${ClusterStack}-FargateClusterName"
                ServiceName:
                  Fn::ImportValue:
                    !Sub "${ServiceStack}-ServiceName"
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-ECSDeployPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
                - ecs:DescribeServices
                - ecs:DescribeTaskDefinition
                - ecs:DescribeTasks
                - ecs:RegisterTaskDefinition
                - ecs:UpdateService
                - ecs:ListTasks
                - ecs:TagResource
              Resource: '*'
        - PolicyName: !Sub "${AWS::StackName}-CodeStarPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - codestar-connections:UseConnection
                Resource: !Ref CodeStartConnection
        - PolicyName: !Sub "${AWS::StackName}-CodePipelinePolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - cloudformation:*
                  - iam:PassRole
                Resource: '*'
        - PolicyName: !Sub "${AWS::StackName}-CodePipelineBuildPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:StartBuild
                  # even though Batch is not active, AWS still requires this permission
                  - codebuild:BatchGetBuilds
                Resource: !Join
                  - ''
                  - - 'arn:aws:codebuild:'
                    - !Ref 'AWS::Region'
                    - ':'
                    - !Ref 'AWS::AccountId'
                    - ':project/*'
                    - !Ref CodeBuildBakeImage

Conclusion

The benefits of Continuous Delivery make it a crucial practice for DevOps teams aiming for faster, safer, and more efficient software releases. By automating deployments, improving collaboration, and reducing risks, CD empowers organizations to innovate with confidence.

πŸ“’ Up next: Stay tuned for how to automate infrastructure with Infrastructure as Code (IaC) !

Leave a Reply