Zero Down Time Deployment!

azibom

Mohammad Reza

Posted on October 31, 2022

Zero Down Time Deployment!

First Step

let's consider we have one nginx and one php node and we want to deploy on php node without down time
Our structure is like this

.
├── app
│   └── index.php
├── docker-compose.yml
├── nginx.Dockerfile
├── nginx.conf
└── php.Dockerfile
Enter fullscreen mode Exit fullscreen mode

That is our docker-compose.yml

version: "3.7"

services:
    cicd-nginx:
        build:
          context: .
          dockerfile: nginx.Dockerfile
        ports:
            - "88:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

    cicd-php:
        build:
          context: .
          dockerfile: php.Dockerfile
Enter fullscreen mode Exit fullscreen mode

That is our nginx.Dockerfile

FROM nginx:1.17-alpine

WORKDIR /app
COPY ./app ./
Enter fullscreen mode Exit fullscreen mode

That is our nginx.conf

server {
    listen 80;
    index index.php index.html;

    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    root /app;
    server_name localhost;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass cicd-php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

    }
}
Enter fullscreen mode Exit fullscreen mode

That is our simple php.Dockerfile

FROM php:8.1-fpm-alpine

WORKDIR /app
COPY ./app ./
Enter fullscreen mode Exit fullscreen mode

And at the end that is our simple index.php

<?php

echo "hi"

?>
Enter fullscreen mode Exit fullscreen mode

Let's go to the next interesting part

Image description

First we change build with image because we want to first build the image and then just replace the new one with the old one, so our docker-compose.yml will become like this:

version: "3.7"

services:
    cicd-nginx:
        image: ${NGINX_IMAGE_TAG}
        ports:
            - "88:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

    cicd-php:
        image: ${PHP_IMAGE_TAG}
Enter fullscreen mode Exit fullscreen mode

First set the variables and the need to build the images

export PHP_IMAGE_TAG=phptest:1
export NGINX_IMAGE_TAG=nginxtest:1

docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
docker build -f nginx.Dockerfile -t $NGINX_IMAGE_TAG .
Enter fullscreen mode Exit fullscreen mode

Then we just run up -d

Now I want to show you what will happen if you just want to deploy the new code

First we prepare a bash script for it (./script.sh):

#!/bin/bash
while :
do
    echo "Your response : "
    curl 127.0.0.1:88
    echo " "
    date
    sleep 2
done
Enter fullscreen mode Exit fullscreen mode

Now we just change the php file to sth like

<?php

echo "hi hi :)"

?>
Enter fullscreen mode Exit fullscreen mode

And then we try to build it with new name

export PHP_IMAGE_TAG=phptest:2
docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
Enter fullscreen mode Exit fullscreen mode

Now we want run the script and then run these commands to update the php container

sh ./script.sh
Enter fullscreen mode Exit fullscreen mode

And

docker-compose down
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Result

You will see sth like this and that is the problem

...
Your response : 
hi 
Mon Oct 31 00:31:23 UTC 2022
Your response : 
hi 
Mon Oct 31 00:31:25 UTC 2022
Your response : 
hi 
Mon Oct 31 00:31:27 UTC 2022
Your response : 
hi 
Mon Oct 31 00:31:30 UTC 2022
Your response : 
curl: (7) Failed to connect to localhost port 88: Connection refused

Mon Oct 31 00:31:32 UTC 2022
Your response : 
curl: (7) Failed to connect to localhost port 88: Connection refused

Mon Oct 31 00:31:34 UTC 2022
Your response : 
curl: (7) Failed to connect to localhost port 88: Connection refused

Mon Oct 31 00:31:36 UTC 2022
Your response : 
curl: (7) Failed to connect to localhost port 88: Connection refused

Mon Oct 31 00:31:38 UTC 2022
Your response : 
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.17.10</center>
</body>
</html>

Mon Oct 31 00:31:40 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:31:42 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:31:44 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:31:46 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:31:48 UTC 2022
...
Enter fullscreen mode Exit fullscreen mode

You can feel what is the problem right now so let's try to fix it

Image description

We want to use docker swarm

First init it

docker swarm init
Enter fullscreen mode Exit fullscreen mode

Then update your docker-compose.yml like this:

version: "3.7"

services:
    cicd-nginx:
        image: ${NGINX_IMAGE_TAG}
        ports:
            - "88:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
        deploy:
          mode: replicated
          replicas: 2
          update_config:
            order: start-first
            failure_action: rollback
            delay: 5s

    cicd-php:
        image: ${PHP_IMAGE_TAG}
        deploy:
          mode: replicated
          replicas: 2
          update_config:
            order: start-first
            failure_action: rollback
            delay: 5s
Enter fullscreen mode Exit fullscreen mode

Then run this command and now you are up

docker stack deploy -c docker-compose.yml <stack_name>
Enter fullscreen mode Exit fullscreen mode

Let's look at containers

CONTAINER ID   IMAGE                   COMMAND                  CREATED              STATUS              PORTS                                       NAMES
6cc2b3516a3a   phptest:2               "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                    website_cicd-php.1.ijd0lpj995318hpffx6gx5t44
7cffa4115598   phptest:2               "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                    website_cicd-php.2.jbd2sg2qm6ounji76q99fk2ql
fcedfb42b44a   nginxtest:1             "nginx -g 'daemon of…"   About a minute ago   Up About a minute   80/tcp                                      website_cicd-nginx.2.8z4spuco9rtdxcpc8y0fux79p
99e1642cf461   nginxtest:1             "nginx -g 'daemon of…"   About a minute ago   Up About a minute   80/tcp                                      website_cicd-nginx.1.eo6erxose7asze8ref2poracc
Enter fullscreen mode Exit fullscreen mode

We have 2 php node and 2 nginx node :)
Let's update the php node and see the result
Change the php file and add one hi

<?php

echo "hi hi hi :)"

?>
Enter fullscreen mode Exit fullscreen mode

And then we try to build php node it with new name

export PHP_IMAGE_TAG=phptest:3
docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
Enter fullscreen mode Exit fullscreen mode

Now we want run the script and then run the command to update the php container

sh ./script.sh
Enter fullscreen mode Exit fullscreen mode

And

docker stack deploy -c docker-compose.yml <stack_name>
Enter fullscreen mode Exit fullscreen mode

Result

...
Your response : 
hi hi :) 
Mon Oct 31 00:53:53 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:53:55 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:53:57 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:53:59 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:54:01 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:03 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:54:05 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:07 UTC 2022
Your response : 
hi hi :) 
Mon Oct 31 00:54:09 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:11 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:13 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:15 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:17 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:19 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:21 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:23 UTC 2022
Your response : 
hi hi hi:) 
Mon Oct 31 00:54:25 UTC 2022
...
Enter fullscreen mode Exit fullscreen mode

Wow And that is the magic
We do not have any down time and docker swarm completely handle it for us

let's make .gitlab-ci.yml for it

Step one

Create a project in gitlab

Step two

Add project to the git and then push it

git init
git add .
git commit -m "init"
git remote add origin git@gitlab.com:azibom/cicd.git
Enter fullscreen mode Exit fullscreen mode

Step three

Add .gitlab-ci.yml to the project

image: docker:20.10.16

stages:
  - publish
  - deploy

variables:
  PHP_IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:php-$CI_COMMIT_SHORT_SHA
  NGINX_IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:nginx-$CI_COMMIT_SHORT_SHA
  DOCKER_TLS_CERTDIR: "/certs"

services:
  - docker:20.10.16-dind

before_script:
  - 'command -v ssh-agent >/dev/null || ( apk add --update openssh )' 
  - eval $(ssh-agent -s)
  - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh

publish:
  stage: publish
  script:
    - docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
    - docker build -f nginx.Dockerfile -t $NGINX_IMAGE_TAG .
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - docker push $PHP_IMAGE_TAG
    - docker push $NGINX_IMAGE_TAG

deploy:
  image: alpine:latest
  stage: deploy
  script:
    - ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "cd $PROJECT_DIR && docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY && docker pull $NGINX_IMAGE_TAG && docker pull $PHP_IMAGE_TAG && export PHP_IMAGE_TAG=$PHP_IMAGE_TAG && export NGINX_IMAGE_TAG=$NGINX_IMAGE_TAG && docker stack deploy --with-registry-auth -c docker-compose.yml website"
  only:
    - master
Enter fullscreen mode Exit fullscreen mode

Step Four

Add some variables to the gitlab ci from setting

PROJECT_DIR && SERVER_IP && SERVER_USER && SSH_PRIVATE_KEY
Enter fullscreen mode Exit fullscreen mode

You can find SSH_PRIVATE_KEY with run this command

cat ~/.ssh/id_rsa
Enter fullscreen mode Exit fullscreen mode

Also run this on server

cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys
Enter fullscreen mode Exit fullscreen mode

Step five

Define your own runner

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh > script.deb.sh
sudo bash script.deb.sh
sudo apt install gitlab-runner
systemctl status gitlab-runner
sudo gitlab-runner register -n \
  --url https://gitlab.com/ \
  --registration-token REGISTRATION_TOKEN \
  --executor docker \
  --description "My Docker Runner" \
  --docker-image "docker:20.10.16" \
  --docker-privileged \
  --docker-volumes "/certs/client"
Enter fullscreen mode Exit fullscreen mode

(bring REGISTRATION_TOKEN from gitlab.com)

Step six

Push to your server and look at pipline

Image description

Step seven , Done

Best wishes

💖 💪 🙅 🚩
azibom
Mohammad Reza

Posted on October 31, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Zero Down Time Deployment!
devops Zero Down Time Deployment!

October 31, 2022