Guest post originally published on Arctiq’s blog by Daniyal Javed, DevOps Engineer and Consultant at Arctiq

Last year I posted a demo of using GitLab CI and ArgoCD with Anthos Config Management. It’s been a popular video on our YouTube Channel.

But, every comment has been asking for the configuration files 😄

Unfortunately, I can’t make the repos public so I will use this blog to go over the configuration.

CI Workflow

CICD Pipelines using Gitlab CI & Argo CD for Anthos workflow architecture

The architecture for this workflow separates CI and CD into two different streams and repositories. When a developer checks in code against the source repository, a GitLab CI job is triggered. This can be achieved using only and except specs in GitLab CI.

An example of building the app where the image is built with the commit SHA everytime there is a merge request against the dev branch.

build_image:
  stage: build_image
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]  
  script:
    - export GOOGLE_APPLICATION_CREDENTIALS=/kaniko/kaniko-secret.json
    - echo $GOOGLE_APPLICATION_CREDENTIALS_BASE64 | base64 -d > $GOOGLE_APPLICATION_CREDENTIALS
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination gcr.io/$GOOGLE_PROJECT_ID/springstore:$CI_COMMIT_SHORT_SHA
  only:
    refs:
      - merge_request
    variables:
      - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "dev"
  except:
    variables:
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "dev"
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "qa"
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "master"

Update tags

Once the image is pushed to the registry (in my case its GCR), our GitLab workflow needs to know the updated image tag. The image tag is stored as an environment variable in GitLab CI. Using GitLab’s API, this variable can be updated.

stage_image_tag:
  stage: Stage Image Tag
  script:
    - curl --request PUT --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/variables/IMAGE_ID" --form "value=${CI_COMMIT_SHORT_SHA}"
  only:
    refs:
      - merge_request
    variables:
      - $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "dev"
  except:
    variables:
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "dev"
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "qa"
      - $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "master"

CD Workflow

Once the image is updated in GitLab CI environment variables, the image is updated and committed to the Anthos Config Management repository. This ACM repository is hooked to Anthos clusters to automatically deploy configurations from Git. I am using envsubst to achieve this. Other tools can also be used.

Deployment to Dev

deploy_dev:
  stage: deploy_dev
  environment:
    name: dev
  image: daniyalj/alpine-envsub:v1
  before_script:
     - git config --global user.email "gitlab@gitlab.com"
     - git config --global user.name "Mr GitLab"
     - mkdir -p ~/.ssh
     - echo "$GIT_SSH_PRIV_KEY" > ~/.ssh/id_rsa
     - chmod 400 ~/.ssh/id_rsa
     - ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
  script:
    - git clone -b ${ENV_BRANCH} git@gitlab.com:daniyalj/${ENV_GIT_REPO}.git
    - cd ${ENV_GIT_REPO}
    - git checkout -b ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}
    - envsubst < pipeline-staging/spring-store.yaml > config-management/namespaces/argocd/spring-store-app-${CI_ENVIRONMENT_NAME}.yaml
    - git add . && git commit -m "deploy ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}" && git push -o merge_request.label="Deploy" -o merge_request.create -o merge_request.target=${ENV_BRANCH} --force origin ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}
  only:
  - dev

The ArgoCD application within ACM looks like this:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: spring-store-dev
  annotations:
    configmanagement.gke.io/cluster-selector: selector-env-dev
spec:
  destination:
    namespace: spring-store
    server: https://kubernetes.default.svc
  project: default
  source:
    chart: spring-store
    helm:
      parameters:
      - name: imageTag
        value: "bab2517a"
      - name: hostname
        value: springstore.dev.anthos.daniyalj.team.arctiq.ca
    repoURL: https://spring-store.storage.googleapis.com/charts
    targetRevision: "4.0.0"
  syncPolicy:
    automated:
      selfHeal: true

Where the image tag is replaced with commit short SHA. Once this change is commited to ACM, ArgoCD will detect the change, synchronize the application and deploy to the dev cluster which is specified with configmanagement.gke.io/cluster-selector: selector-env-dev in ACM.

At this point, the application can be verified in dev. Once, happy with the change, the merge request can be merged to master which will trigger the production deployment.

Deployment to Prod

deploy_dev:
  stage: deploy_prod
  environment:
    name: dev
  image: daniyalj/alpine-envsub:v1
  before_script:
     - git config --global user.email "gitlab@gitlab.com"
     - git config --global user.name "Mr GitLab"
     - mkdir -p ~/.ssh
     - echo "$GIT_SSH_PRIV_KEY" > ~/.ssh/id_rsa
     - chmod 400 ~/.ssh/id_rsa
     - ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
  script:
    - git clone -b ${ENV_BRANCH} git@gitlab.com:daniyalj/${ENV_GIT_REPO}.git
    - cd ${ENV_GIT_REPO}
    - git checkout -b ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}
    - envsubst < pipeline-staging/spring-store.yaml > config-management/namespaces/argocd/spring-store-app-${CI_ENVIRONMENT_NAME}.yaml
    - git add . && git commit -m "deploy ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}" && git push -o merge_request.label="Deploy" -o merge_request.create -o merge_request.target=${ENV_BRANCH} --force origin ${CI_ENVIRONMENT_NAME}-${CI_PIPELINE_ID}
  only:
  - master
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: spring-store-prod
  annotations:
    configmanagement.gke.io/cluster-selector: selector-env-prod
spec:
  destination:
    namespace: spring-store
    server: https://kubernetes.default.svc
  project: default
  source:
    chart: spring-store
    helm:
      parameters:
      - name: imageTag
        value: "bab2517a"
      - name: hostname
        value: springstore.prod.anthos.daniyalj.team.arctiq.ca
    repoURL: https://spring-store.storage.googleapis.com/charts
    targetRevision: "4.0.0"
  syncPolicy:
    automated:
      selfHeal: true

And as dev, once this change is detected, ACM updates ArgoCD and ArgoCD deploys the new instance in prod.

Hope this blog summarizes the workflow covered in the video. If you want to discuss this topic further please feel free to reach out, we would love to hear from you.