GitOps with Attini

What is GitOps?

GitOps is a way of working with Operations which enables a high level of automation and complex workflows, you can say that it is the “DevOps version of GitFlow”.

The core logic is that you have one git branch per environment example dev, stage and main (main goes to production). When you merge code into the dev branch, your code automatically deploys to the dev environment.

When you are happy with your changes, you merge the dev branch into the stage branch that automatically deploys to the stage environment.

And when it is time to deploy to production you simply merge the stage branch into the main branch that deploys to the production environment.


When is GitOps good?

GitFlow was a way of working designed for monotonic applications (big code base and a lot of people working on it), and GitOps share the same advantages. It is allows for many DevOps engineers to work on one codebase and the DevOps engineers can then choose to deploy their own changes in their own pace without blocking each other.

It is also very good to be able to manage deploys via a tool (git) that all DevOps engineers are (or should be) familiar with.


When is GitOps bad?

GitOps will have the same drawbacks as GitFlow, for example it will often result in a lots of work just managing git (resolving conflicts, cheery picking commits for merging etc).

Sometimes it can be difficult to test, for example: If you do a code change that is dependent on an other change that is currently only in the dev and stage environment (not in prod). Then your change will be successful in the test environments, but it will crash in production.

It is very common that low priority task get “parked” in development environments for a long time, these changes accumulate over time and all of a sudden, your tests become unreliable because the development environments differ a lot from production.

A common criticism of GitFlow that is relevant for GitOps as well is that your do a “new build” for every environment, meaning that you do not deploy the same artifacts to production that you have tested. You just hope that “the same code should create the same artifact”. This is a problematic assumption because even if your code is the same, the rest of your environment might not be. There can be new versions of software library’s that you depend on that gets automatically updated, the build server or build scrips can have been updates etc.

In software development the general trend is to leave GitFlow in favor for microservices and promotion* based ways of working because it allows for development teams to deliver better quality and a high pace.

* promotion based way of working is when you only build your artifacts (ex docker images) once, then you deploy that exact artifact into your different environments.


How to set up GitOps with Attini?

It is easy to configure a build server using the Attini CLI to perform a GitOps workflow.

Architecture

GitOpsSetup

Setup

  1. Set upp a build server (AWS CodeBuild, Jenkins etc) with a git integrations so that git can trigger builds.

  2. Give the build server an AWS IAM Role or AWS IAM User (using AWS Access Keys).

  3. Give the AWS IAM Role or AWS IAM User the IAM permission sts:AssumeRole.

  4. Set up a deploy-role in all your environments, see our CloudFormation example

  5. Run a script on your build server that assumes the right role depending on your git branch, see our example deploy script.


Example code

Deploy role example CloudFormation template

This role uses a managed policy that is created by the attini-setup which has the minimum required access to do a Attini deploy.

Note

You have to configure the Principal in the AssumeRolePolicyDocument to allow for your build server role to assume it.

AWSTemplateFormatVersion: 2010-09-09
Description: Role for Attini deployment

Resources:
  AttiniDeploymentRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub attini-deploy-role-${AWS::Region}
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              AWS: ${Build server user or role arn} # This is were you configure the trust with your build server
        Version: '2012-10-17'
      ManagedPolicyArns:
        - !Sun arn:aws:iam::${AWS::AccountId}:policy/attini-cli-user-${AWS::Region} # this policy is created by the attini-setup CloudFormation stack

Example deploy script

Note

You have to configure the environment variables for “branch name ($BRANCH)”, “environment name”, “individual target roles” and the path you your code ($PATH_TO_DISTRIBUTION).

assume_role () {
    echo "Assuming role $1"
    aws sts assume-role --role-arn $1 --role-session-name deploy-$DISTRIBUTION_NAME > ~/.aws-assumed-role-credentials
    RETURN_CODE=$?
    if [ $RETURN_CODE != 0 ]; then
        exit $RETURN_CODE
    fi
    export AWS_ACCESS_KEY_ID=`jq '.Credentials.AccessKeyId' ~/.aws-assumed-role-credentials --raw-output`
    export AWS_SECRET_ACCESS_KEY=`jq '.Credentials.SecretAccessKey' ~/.aws-assumed-role-credentials --raw-output`
    export AWS_SESSION_TOKEN=`jq '.Credentials.SessionToken' ~/.aws-assumed-role-credentials --raw-output`
    rm -f ~/.aws-assumed-role-credentials
}

clear_credentials () {
    echo "Cleaning upp credentials"
    unset AWS_ACCESS_KEY_ID
    unset AWS_SECRET_ACCESS_KEY
    unset AWS_SESSION_TOKEN
}


if [ "$BRANCH" == "dev" ]; then
    echo "Deploying to dev"
    assume_role arn:aws:iam::{DevAccountId}:role/{RoleName} # The role in the development account
    attini deploy run --environment "dev" $PATH_TO_DISTRIBUTION
    RETURN_CODE=$?

elif [ "$BRANCH" == "stage" ]; then
    echo "Deploying to stage"
    assume_role arn:aws:iam::{StageAccountId}:role/{RoleName}  # The role in the stage account
    attini deploy run --environment "acc" $PATH_TO_DISTRIBUTION
    RETURN_CODE=$?

elif [ "$BRANCH" == "main" ]; then
    echo "Deploying to prod"
    assume_role arn:aws:iam::{ProdAccountId}:role/{RoleName} # The role in the prod account
    attini deploy run --environment "prod" $PATH_TO_DISTRIBUTION
    RETURN_CODE=$?

else
    echo "Branch did not map to any pre-configured environment"
    RETURN_CODE=1
fi

clear_credentials

exit $RC