Zero Down Time Deployment!
Tags: devops, linux, automation, php
Published: 2022-10-31 04:24:47

## 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
```bash
.
├── app
│   └── index.php
├── docker-compose.yml
├── nginx.Dockerfile
├── nginx.conf
└── php.Dockerfile
```
That is our docker-compose.yml
```bash
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
```
That is our nginx.Dockerfile
```bash
FROM nginx:1.17-alpine

WORKDIR /app
COPY ./app ./
```
That is our nginx.conf
```bash
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;

    }
}
```
That is our simple php.Dockerfile
```bash
FROM php:8.1-fpm-alpine

WORKDIR /app
COPY ./app ./
```
And at the end that is our simple index.php
```php
<?php

echo "hi"

?>
```
## Let's go to the next interesting part

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3izl8zvepshp4ihk1h5p.gif)

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:
```bash
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}
```
First set the variables and the need to build the images
```bash
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 .
```
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):
```bash
#!/bin/bash
while :
do
	echo "Your response : "
	curl 127.0.0.1:88
	echo " "
	date
	sleep 2
done
```
Now we just change the php file to sth like
```php
<?php

echo "hi hi :)"

?>
```
And then we try to build it with new name
```bash
export PHP_IMAGE_TAG=phptest:2
docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
```
Now we want run the script and then run these commands to update the php container
```bash
sh ./script.sh
```
And
```bash
docker-compose down
docker-compose up -d
```
## Result
You will see sth like this and that is the problem
```bash
...
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
...
```
#### You can feel what is the problem right now so let's try to fix it

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q3tk4y9jr26g9unjwnc.gif)

## We want to use docker swarm
First init it
```bash
docker swarm init
```
Then update your docker-compose.yml like this:
```bash
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
```
Then run this command and now you are up
```bash
docker stack deploy -c docker-compose.yml <stack_name>
```
Let's look at containers
```bash
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
```
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
<?php

echo "hi hi hi :)"

?>
```
And then we try to build php node it with new name
```bash
export PHP_IMAGE_TAG=phptest:3
docker build -f php.Dockerfile -t $PHP_IMAGE_TAG .
```
Now we want run the script and then run the command to update the php container
```bash
sh ./script.sh
```
And
```bash
docker stack deploy -c docker-compose.yml <stack_name>
```
## Result
```bash
...
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
...
```
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
```bash
git init
git add .
git commit -m "init"
git remote add origin git@gitlab.com:azibom/cicd.git
```
#### Step three
Add `.gitlab-ci.yml` to the project
```bash
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
```
#### Step Four
Add some variables to the gitlab ci from setting
```bash
PROJECT_DIR && SERVER_IP && SERVER_USER && SSH_PRIVATE_KEY
```
You can find `SSH_PRIVATE_KEY` with run this command
```bash
cat ~/.ssh/id_rsa
```
Also run this on server
```bash
cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys
```
#### 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"
```
(bring REGISTRATION_TOKEN from gitlab.com)
#### Step six
Push to your server and look at pipline

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5m2m479qt7jdf89nsfqt.png)
#### Step seven , Done
Best wishes