Guide to rolling out single and multiple applications

Hello everyone! This is Yuri Shakhov, DevOps engineer at Flant. Recently, I needed to organize a seamless deployment of client applications. I studied various approaches for this and settled on the blue-green deployment strategy. But the problem was that I could not find materials with practical examples; the articles I found described only theoretical aspects. So I had to study the blue-green deployment approach myself. And now I wanted to share this experience.

In this article, I will deploy the application to blue-green and also show how the switch between blue and green works in practice. I will not consider various deployment strategies and their advantages. To get acquainted with the theory of blue-green and other variations, I recommend reading our material on various deployment strategies in Kubernetes.

I have divided this article into two parts: first, we will consider the implementation of deploying an application in the blue-green strategy, and then we will try werf bundle for deploying several applications from one repository. There are different ways to implement this strategy, you can use additional tools such as Service Mesh, Argo CD and others. I will deploy using werfdescribe all resources as Helm templates, and use GitLab for deployment. It is assumed that the reader is familiar with these technologies. The peculiarity here is that native entities and mechanisms in the form of labels for Kubernetes are used. Further, green and blue will be called “versions” of the application. Also, in this article, we will not consider issues of database migration, although for some applications this may be necessary.

Simple blue-green

Let's assume that we have an application and we want to deploy it. We will do this in two stages:

  1. Deploy the application itself (deploy_app), such as Deployment and Service.

  2. Change version: deploy Ingress with the desired Service name (deploy_ingress). At this stage, traffic will be switched between application versions.

Let's represent this as a pipeline:

To implement this, we define a variable deploy_version (we will take the value from GitLab CI, which we will consider later), which will be equal to blue or green and will be substituted into Helm templates. For Deployment and Service we add labels:

{{ $deploy_version := "" }}
{{ if .Values.werf.deploy_version }}
{{ $deploy_version = print "-" .Values.werf.deploy_version }}
{{ end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}{{ $deploy_version }}
  labels:
    app: {{ .Chart.Name }}{{ $deploy_version }}
...
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }}{{ $deploy_version }}
spec:
  selector:
    app: {{ .Chart.Name }}{{ $deploy_version }}
...

In order for traffic to reach the pod, we need to create an Ingress. To access a specific version of the application, we will specify a Service with the desired name (blue or green). In this case, the Ingress template will look like this:

{{ $deploy_version := "" }}
{{ if .Values.werf.deploy_version }}
{{ $deploy_version = print "-" .Values.werf.deploy_version  }}
{{ end }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example
  labels:
    deploy-version: {{ .Values.werf.deploy_version | quote }}
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ .Chart.Name }}{{ $deploy_version }}
            port:
              name: http
 tls:
  - hosts:
    - example.com
    secretName: {{ .Chart.Name }}-tls

Instead of changing the Ingress, you can change the Service to direct traffic to blue or green Deployment by labels, but it is not recommended to do this. In this case, we will lose the ability to access the Deployment by the Service name in the cluster. This can be a problem for testing the correctness of the update, since it will not be possible to access the new version before all traffic is directed to it.

You can also create a second Ingress leading to an inactive version with a different domain for testing. In this case, you need to close it behind authorization to restrict access.

Now let's look at the pipeline. When deploying an application, you need to declare a variable deploy_version — the version to which the deployment will be performed. For werf, this can be done like this:

werf converge --set "werf.deploy_version=${DEPLOY_VERSION}"

Also during deployment we will check that the version is not active, that is, there is no traffic to it, and our rollout will not affect users. The check itself is implemented as follows: we receive information about which Service the Ingress running in the cluster points to, and find there blue or green.

Below is an example of a complete gitlab-ci.yml:

stages:
  - deploy_app
  - deploy_ingress

.check_upstreams: &check_upstreams
  - APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress example --output=custom-columns="SVCs:..service.name" --no-headers --ignore-not-found | awk -F '-' {'print $NF'})

.deploy_app:
  stage: deploy_app
  script:
    - *check_upstreams
    - if [[ ${KUBE_CURRENT_ACTIVE} == ${UPSTREAM} ]];
      then
        tput setaf 9 && echo "Обнаружена попытка деплоя на активную версию, деплой будет остановлен!" && exit 1;
      else
        werf converge \
          --release example-${UPSTREAM} \
          --set "werf.deploy_version=${UPSTREAM}";
      fi;
  allow_failure: false

.deploy_ingress:
  stage: converge_ingresses
  script:
    - *check_upstreams
    - if [ ${APP_CURRENT_ACTIVE} == ${DEPLOY_VERSION} ];
      then
        tput setaf 9 && echo "Обнаружена попытка переключения на активную версию, деплой будет остановлен!" && exit 1;
      else
        werf converge
        --set "werf.deploy_version=${DEPLOY_VERSION}"
      fi;

Deploy to blue:
  extends: .deploy_app
  environment:
    name: production
  variables:
    UPSTREAM: "blue"

Deploy to green:
  extends: .deploy_app
  environment:
    name: production
  variables:
    UPSTREAM: "green"

Switch to blue:
  extends: .deploy_ingress
  environment:
    name: production
  variables:
    DEPLOY_VERSION: "blue"

Switch to green:
  extends: .deploy_ingress
  environment:
    name: production
  variables:
    DEPLOY_VERSION: "green"

What we ended up doing:

  1. Adjusted Helm templates for Deployment, Service and Ingress, adding the “color” of our version to them.

  2. We wrote a CI that:

    • deploys the application to blue And green;

    • deploy Ingress, which switches traffic to the desired version;

    • checks that deployments do not occur on the active version.

Now let's move on to the part with bundles.

Deploying multiple applications with werf bundle

Why might bundles be needed? Let's say a client needs to deploy several applications together, and it is more convenient to do this from one repository. The bundle mechanism allows you to publish an application chart and deploy it later without access to a specific Git repository. All that is required is access to the container registry where the bundle is stored. This approach simplifies the process of delivering an application chart.

We will pack applications using werf bundle. We will not dwell on the description of the tool in detail, you can read about its advantages and use cases in the documentation.

Bundle creation occurs in the main application repository, here we will focus only on deployment. In the CI file, we will specify the names of the applications and the corresponding variables for each of them: repository, bundle tag and Ingress name:

variables:
  FIRST_REPO_BUNDLE: registry.gitlab.awesome.ru/frontend/first
  FIRST_TAG: "0.1"
  FIRST_INGRESS: first
...

# apps_for_matrix & apps_for_bash должны содержать одинаковые значения!

.apps_for_matrix: &apps_for_matrix
  ["FIRST", "SECOND", "THIRD", "FOURTH", "FIFTH"]

.apps_for_bash: &apps_for_bash
  APPLICATIONS=("FIRST", "SECOND", "THIRD", "FOURTH", "FIFTH")

Compared to the first part, there will already be three stages in the pipeline. Since we are deploying several applications from one repository, we need to make sure that they are all in the same state. To do this, we will implement a version status check job, we will call it check_upstream:

At this stage the following conditions must be met:

  • The active version is the same for all applications.

  • The application has no active versions (assuming it has not yet been deployed to the cluster).

stages:
  - check_upstreams
  - deploy_apps
  - deploy_ingresses

.base_werf: &base_werf
  - set -x
  - type trdl && source $(trdl use werf 2)
  - werf version
  - type werf && source $(werf ci-env gitlab --verbose --as-file)

.check_upstreams: &check_upstreams
  - *base_werf
  - *apps_for_bash
  - |
    GREEN=false
    BLUE=false
    EMPTY=0

    for APP in ${APPLICATIONS[@]}
    do
      REPOSITORY_INGRESS=${APP}_INGRESS
      APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress ${!REPOSITORY_INGRESS} --output=custom-columns="SVCs:..service.name" --no-headers --ignore-not-found | awk -F '-' {'print $NF'})

      EMPTY=$((EMPTY+1))
      if [[ ${APP_CURRENT_ACTIVE} == "green" ]];
        then GREEN=true;
      elif [[ ${APP_CURRENT_ACTIVE} == "blue" ]];
        then BLUE=true;
      elif [[ -z ${APP_CURRENT_ACTIVE} ]];
        then EMPTY=$((EMPTY-1));
      else
        tput setaf 9 && echo "Что-то пошло не так! Статус версий некорректен" && exit 1;
      fi;
    done

    if [[ ${GREEN} != ${BLUE} ]];
      then
      if [[ ${GREEN} ]]
        COLOR="green"
        then tput setaf 14 && echo "Статус версий для приложений одинаков — green, можно продолжать деплой";
      elif [[ ${BLUE} ]]
        COLOR="blue"
        then tput setaf 14 && echo "Статус версий для приложений одинаков — blue, можно продолжать деплой";
      fi;
    elif [[ ${EMPTY} = 0 ]]
      then tput setaf 14 && echo "Ingress для данных приложений в кластере не обнаружено, можно продолжать деплой";
    else
      tput setaf 9 && echo "Статус версий для приложений отличается, деплой будет остановлен!!!" && exit 1;
    fi;

Check_upstreams:
  stage: check_upstreams
  script:
    - *check_upstreams
  environment:
    name: production
  when: always
  allow_failure: false

The application will be deployed using a bundle. We pass all the necessary arguments to this command and do not forget to specify a different release name (flag --release) for different applications, otherwise the deployment of one will overwrite the deployment of the previous one. Then, using parallel:matrix At the deployment stage, the required number of deployment jobs will be automatically created based on the number of applications:

Below is an example of implementing application deployment in CI:

.deploy_apps: &deploy_apps
  stage: deploy_apps
  before_script:
    - *base_werf
    - REPOSITORY_BUNBLE=${REPOSITORY_NAME}_REPO_BUNDLE
    - REPOSITORY_TAG=${REPOSITORY_NAME}_TAG
    - REPOSITORY_INGRESS=${REPOSITORY_NAME}_INGRESS
    - APP_CURRENT_ACTIVE=$(werf kubectl -n ${WERF_NAMESPACE} get ingress ${!REPOSITORY_INGRESS} --output=custom-columns="SVCs:..service.name" --no-headers --ignore-not-found | awk -F '-' {'print $NF'})
    - |
      if [[ ${APP_CURRENT_ACTIVE} = ${DEPLOY_VERSION} ]];
        then tput setaf 9 && echo "Обнаружена попытка деплоя на активную версию, деплой будет остановлен!!!" && exit 1;
      fi;
  script:
    - werf cr login -u nobody -p ${BUNDLE_PULLER_PASSWORD} ${!REPOSITORY_BUNBLE}
    - werf bundle apply
      --release $(echo ${!REPOSITORY_BUNBLE} | cut -d / -f4)-${DEPLOY_VERSION}-${CI_ENVIRONMENT_SLUG}
      --repo ${!REPOSITORY_BUNBLE}
      --tag ${!REPOSITORY_TAG}
      --set "werf.deploy_version=${DEPLOY_VERSION}"
  when: manual

Deploy to Green:
  extends: .deploy_apps
  stage: deploy_apps
  environment:
    name: production
  parallel:
    matrix:
      - REPOSITORY_NAME: *apps_for_matrix
  variables:
    DEPLOY_VERSION: "green"

This is how we got a pipeline that allows us to deploy different applications from previously published bundles from one repository.

Conclusion

Blue-green helps you roll out updates to your applications reliably and quickly. This strategy simplifies the process and allows you to test a new version before its full launch. And bundles are especially useful for deploying several applications at once. This makes management and updating more visual and centralized, which is especially important for large projects.

In this article, we looked at deploying applications in a blue-green strategy using GitLab-CI and modified our CI to deploy multiple applications from a single repository. This guide helps you write CI and deploy your application from GitLab. I hope you find it useful.

P.S.

Read also in our blog:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *