GitLab CI/CD has many functions, although the primary goal of the tool is to build a continuous environment using these methodologies:
Stages & Requirements
The complete CI/CD flow is divided into several stages and requirements. The requirements are for you to have the following entities:
- Gitlab SaaS account or Self-hosted account
- A registry to save the image-build (you can use a public registry such as a Docker registry)
- Kubernetes cluster spawn using GKE
- GitLab pipeline to build the flow
Whereas the stages of CI/CD development are explained as the following:
- Build Stage: The stage to build the required image as a service, such as Nginx Image to serve as a web server, fpm image to process the PHP script execution, etc
- Test Stage / Scan Test: The stage to test of the build result for the related image
- Deploy Stage: The stage to deliver the image build to the Kubernetes cluster
build Stage
If you’ve done created an account for GitLab, please create file .gitlab-ci.yaml
on your working branch or the main branch.
Example Directory structure for image build
- Ubuntu Build
## Ubuntu Build Dockerfile
FROM ubuntu:22.04
# Init preparation
RUN apt-get update && apt-get upgrade -y \
&& apt-get install software-properties-common apt-transport-https less net-tools telnet -y \
&& add-apt-repository ppa:ondrej/php -y && apt-get update \
&& apt-get install inetutils-ping vim nano wget \
zip gnupg mysql-client -y
- FPM Build
## FPM Build Dockerfile
FROM index.docker.io/sultanahmad/ubuntu-base:v4
# Init preparation
RUN apt-get update && apt-get upgrade -y \
&& apt-get install php8.0 php8.0-common php8.0-cli php8.0-xml php8.0-mysql \
php8.0-mbstring php8.0-bcmath php8.0-fpm php8.0-readline php8.0-curl -y
#
# Config PHP-FPM
WORKDIR /etc/php/8.0/fpm/pool.d
RUN cp www.conf www.conf.orig
RUN sed -i "s/\/run\/.*/0.0.0.0:9000/g" www.conf && service php8.0-fpm start
#
# Data migration and preparation
WORKDIR /usr/local/bin
RUN wget https://getcomposer.org/download/2.4.2/composer.phar
RUN mv composer.phar composer && chmod +x composer
RUN mkdir -pv /var/www/html/travellist
RUN mkdir -pv /apps
COPY web-data/ /apps/
WORKDIR /apps
RUN echo yes | composer install
- Nginx Build
## Nginx Build Dockerfile
FROM nginx:latest
RUN apt-get update \
&& apt-get install inetutils-ping mysql-common vim nano -y
Full example code of the build pipeline is as the following lines:
variables:
IMAGE_TAG1: "v3"
KUBE_AGENT_PATH: linuxsa/epic-developer-advocate/kube-agents
IMAGE_NGINX: "index.docker.io/sultanahmad/laravel-nginx:$IMAGE_TAG1"
IMAGE_FPM: "index.docker.io/sultanahmad/laravel-fpm:$CI_BUILD_REF_NAME"
IMAGE_UBUNTU: "index.docker.io/sultanahmad/ubuntu-base:$CI_BUILD_REF_NAME"
NGINX_PATH: "laravel-build/nginx-build/Dockerfile"
FPM_PATH: "laravel-build/fpm-build/Dockerfile"
FPM_PATH_STAGING: "laravel-build/fpm-build/Dockerfile.staging"
UBUNTU_PATH_STAGING: "ubuntu-build/Dockerfile.staging"
UBUNTU_PATH: "ubuntu-build/Dockerfile"
CI_REGISTRY: "index.docker.io"
UBUNTU_CONTEXT_DIR: "dir://ubuntu-build"
FPM_CONTEXT_DIR: "dir://laravel-build/fpm-build"
NGINX_CONTEXT_DIR: "dir://laravel-build/nginx-build"
SAST_IMAGE_SUFFIX: '-fips'
SAST_EXCLUDED_ANALYZERS: "spotbugs, bandit, brakeman, gosec, semgrep, security-code-scan, flawfinder"
stages:
- build
- release
build-ubuntu-image:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
stage: build
environment: staging
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context=$UBUNTU_CONTEXT_DIR
--dockerfile=$UBUNTU_PATH_STAGING
--destination "${IMAGE_UBUNTU}"
tags:
- mamad
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_TITLE =~ /^redeploy/
- if: $CI_COMMIT_TITLE =~ /^build image/
build-fpm-image:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
stage: build
environment: staging
variables:
APP_URL: travellist-staging.ahmadcloud.my.id
needs: [build-ubuntu-image]
before_script:
- sh laravel-build/script.sh
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context=$FPM_CONTEXT_DIR
--dockerfile=$FPM_PATH_STAGING
--destination "${IMAGE_FPM}"
tags:
- mamad
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_TITLE =~ /(?:^|\W)redeploy(?:$|\W)/
- if: $CI_COMMIT_TITLE =~ /^build image/
build-nginx-image:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
stage: build
before_script:
- echo $CI_PROJECT_DIR
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context=$NGINX_CONTEXT_DIR
--dockerfile=$NGINX_PATH
--destination "${IMAGE_NGINX}"
tags:
- mamad
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_TITLE =~ /(?:^|\W)redeploy(?:$|\W)/
- if: $CI_COMMIT_TITLE =~ /^build image/
# Image Production Release
release:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
stage: release
variables:
RELEASE_IMAGE_FPM: "$REGISTRY/$GCR_PROJECT/laravel-fpm:$IMAGE_TAG1"
RELEASE_IMAGE_UBUNTU: "$REGISTRY/$GCR_PROJECT/ubuntu-base:$IMAGE_TAG1"
APP_URL: travellist.ahmadcloud.my.id
DB_USER: user_db
before_script:
- sh laravel-build/script.sh
- export GOOGLE_APPLICATION_CREDENTIALS=/kaniko/config.json
- base64 -d "$GCR_ACCOUNT" > $GOOGLE_APPLICATION_CREDENTIALS
environment: production
script:
- /kaniko/executor
--context=$UBUNTU_CONTEXT_DIR
--dockerfile=$UBUNTU_PATH
--destination "${RELEASE_IMAGE_UBUNTU}"
- /kaniko/executor
--context=$FPM_CONTEXT_DIR
--dockerfile=$FPM_PATH
--destination "${RELEASE_IMAGE_FPM}"
when: manual
tags:
- mamad
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_TITLE =~ /^redeploy/
- if: $CI_COMMIT_TITLE =~ /^build image/
- if: $CI_COMMIT_TITLE =~ /^release image/
Explanation
- The job described on the pipeline above will build an FPM Image, based on the custom Ubuntu build image
- The image registry will be at the Docker registry
- The pipeline for image build is divided into two stages, the build stage is a stage to build the image with the tag
<image tag name>:$IMAGE_TAG1
, while the release stage is a ready image for production purposes. The image tag for the second stage will have a version number on the end of the name, e.g<image tag name>:$IMAGE_TAG2
- Each image build job in gitlab-ci, is triggered by a specified commit title so that the job will not get into the pipeline on every commit
- There is a line to execute the script to replace the image tag from Dockerfile (
sh laravel-build/script.sh
). That strategy allows us to modify the image tag name dynamically by only changing the variable ongitlab-ci.yaml
Pipeline Result
Conclusion
In this part, we have explained the pipeline and the Dockerfile jobs to build the image for the containerized applications.
In the next part, we will have an article to explain the SAST and the continuous delivery integration using GitLab CI.
For more articles and tutorials please visit our website at settingserver.com.
If you have any more inquiries please reach out to us at [email protected]
Leave a Reply