Parallel Deploy To Swarm

The art of parallel deploy to SWARM

Parallel Deploy to SWARM. Pipeline

The following pipeline example allows to deploy SWARM services in parallel which saves a lot of time comparing to

node {
  def repo = "$env.backend_repo";
  def configs = "$env.deploy_repo";  
  def creds = "github-ssh";
  def branch = (env.gitlabMergeRequestLastCommit) ? env.gitlabMergeRequestLastCommit : "${env.BRANCH}";
  def project = "$env.project";
  def project_hub = "$env.docker_hub";
  def image_tag = (env.environment == 'staging') ? "latest" : "prod";
  def envs = [];
  
  stage ("Get app code") {
      dir ("build") {
        checkout scm: [$class: 'GitSCM',
                 userRemoteConfigs: [[url: "${repo}", credentialsId: "github-ssh"]],
                 branches: [[name: "${branch}"]]], changelog: false, poll: false
      }
    }
    
    stage("Get configs") {
       dir("deploy") {
        checkout scm: [$class: 'GitSCM',
                 userRemoteConfigs: [[url: "${configs}", credentialsId: "github-ssh"]],
                 branches: [[name: "master"]]], changelog: false, poll: false
       }
       
       dir('build') {
            //Get .env file
            configFileProvider(
                  [configFile(fileId: "${env.environment}-docker-env", variable: 'envs')]) {
                   sh 'cp ${envs} ./.env';
            }
            
            for (String i : readFile('.env').split("\r?\n")) {
                if(i != "") {
                    envs.push(i);
                }
            }
            sh "cp ../deploy/dockerfiles/Backend.Dockerfile ./Backend.Dockerfile";
            sh "cp ../deploy/configs/default_nginx.conf ./config/default_nginx.conf";
            sh "cp ../deploy/dockerfiles/Proxy.Dockerfile ./Proxy.Dockerfile"
            sh "cp ../deploy/dockerfiles/Test.Dockerfile ./Test.Dockerfile";
            sh "cp ../deploy/configs/database.yml ./config/database.yml";
            sh "cp ../deploy/configs/entrypoint.sh ./entrypoint.sh";
            sh "cp ../deploy/docker-compose.docs.yml ./docker-compose.docs.yml";
            sh "cp ../deploy/dockerfiles/Static.Dockerfile ./Static.Dockerfile";       
        }
    }
    
    //build container
    if(env.BUILD_DOCS == 'yes') {
        stage ("Generate Docs"){
            dir ("build") {
                    def customImage = docker.build("backend:docs", "-f Test.Dockerfile .");
                    def current_dir  = pwd();
                    configFileProvider(
                        [configFile(fileId: 'test-docker-env', variable: 'envs')]) {
                        sh 'cp ${envs} ./.env';
                        sh "docker-compose -f docker-compose.docs.yml up -d";
                        def test_container = docker.image('backend:docs');
                        test_container.inside("--env-file=${current_dir}/.env --network=build_docs") {
                                sh "bundle exec rails db:drop db:create RAILS_ENV=test";
                                sh "bundle exec rake docs:generate SIMPLECOV=false";
                        }
                        sh "docker-compose -f docker-compose.docs.yml stop";
                }
            }
        }
    }     
    
    stage ("Build App") {              
        dir ("build") {
                withCredentials([file(credentialsId: 'cloudfront-key', variable: 'FILE')]) {
                        sh "rm -f ./.cfk";
                        sh 'cp $FILE ./.cfk';
                        sh "chmod 644 .cfk";
                }
                configFileProvider(
                      [configFile(fileId: "${env.environment}-docker-env", variable: 'envs')]) {
                      sh 'cp ${envs} ./.env';
                      def customImage = docker.build ("${project_hub}/${project}/backend:${image_tag}", "-f Backend.Dockerfile .");
                      sh "docker push ${project_hub}/${project}/backend:${image_tag}";

                      def staticImage = docker.build ("${project_hub}/${project}/backend_static:${image_tag}", "-f Static.Dockerfile .");
                      sh "docker push ${project_hub}/${project}/backend_static:${image_tag}";

            }
        }
    }
    
    stage("Deploy app") {

        shCommand("sudo docker pull ${project_hub}/${project}/backend:${image_tag}");
        shCommand("sudo docker pull ${project_hub}/${project}/backend_static:${image_tag}");
        
        def jenvs = envs.join(" --env-add ");
        def services = ["backend", "sidekiq", "rpc", "ws"]
        
        parallel rolling_update(services, jenvs, image_tag)
    }
        

    dir("build") {
            
        GIT_COMMIT = sh (
            script: 'git rev-parse --short HEAD',
            returnStdout: true
        ).trim()

        wrap([$class: 'BuildUser']) {
           sh """curl https://api.rollbar.com/api/1/deploy/ \
          -F access_token=${env.ROLLBAR_KEY} \
          -F environment=${env.environment} \
          -F revision=${GIT_COMMIT} \
          -F local_username=${BUILD_USER}"""      
            sh 'echo "${BUILD_USER}"'
          }
    }
}

def rolling_update(services, jenvs, image_tag) {

  def buildParallelMap = [:]
    for (name in services) {
      buildParallelMap.put(name, update(name, jenvs, image_tag))
    }
    buildParallelMap.put("static", update_static(image_tag))
  
  return buildParallelMap
}

def update(name, jenvs, image_tag) {
    return {
       shCommand("sudo docker service update --env-add ${jenvs} --force --image ${env.docker_hub}/${env.project}/backend:${image_tag} ${env.project}_${name}");
    }
}

def update_static(image_tag) {
    return {
        shCommand("sudo docker service update --force --image ${env.docker_hub}/${env.project}/backend_static:${image_tag} ${env.project}_static");
    }
}

def shCommand(command) {
    sshagent (credentials: ['ssh-key']) {
        def ip = (env.environment == 'staging') ? env.dev_ip : env.prod_ip
        sh "ssh -o StrictHostKeyChecking=no -l $env.user $ip $command"
    }
}