For build, test and deploy Gerrit application we use docker containers and docker-compose for more comfortable work with its. And all pipeline for CI/CD is defained in Jenkinsfile.
So there you can see a Dockerfile for creating our build image based on centos:7 image.
FROM centos:7
WORKDIR /app
COPY . .
RUN yum install curl git zip unzip maven python3 java-11-openjdk-devel -y && \
curl -fsSL https://rpm.nodesource.com/setup_17.x | bash - && \
yum install nodejs -y && \
yum install gcc-c++ make -y && \
npm install -g bower @bazel/bazelisk && \
yum remove java-1.8.0-openjdk-devel -y && \
yum clean all -y
VOLUME ["/root/.cache/bazel/_bazel_root"]
CMD ["/bin/bash", "/app/entrypoint.sh"]
As a work directory we use /app directory which is storing in our repository and copy from them an entrypoint.sh script as an entrypoint step in our image. And the RUN
step for installing all necessary tools for build.
There you can see an entrypoint.sh script there
#!/bin/bash
git clone --recurse-submodules https://gerrit.googlesource.com/gerrit
cd gerrit && bazel build :release
There are some few commands - for cloning repository with application code including submodules and start build application using bazel tool(tool for building and testing code powered by Google).
And also you can see a volume VOLUME ["/root/.cache/bazel/_bazel_root"]
it is using for storing build-cache. But we need it? Because bazel for building code using cache and it rebuild only these things which were changed, so it reduce build and test time.
So there you can see a Dockerfile for creating our application image based on centos:7 image.
FROM centos:7
ADD entrypoint.sh /
ADD gerrit.war /var/gerrit/bin/gerrit.war
RUN yum -y install initscripts && \
yum -y install java-11-openjdk && \
yum -y install git && \
yum -y install openssh-server && \
/entrypoint.sh init && \
rm -f /var/gerrit/etc/{ssh,secure}* && rm -Rf /var/gerrit/{static,index,logs,data,index,cache,git,db,tmp}/* && \
yum -y clean all
ENV CANONICAL_WEB_URL=
ENV HTTPD_LISTEN_URL=
# Allow incoming traffic
EXPOSE 29418 8080
VOLUME ["/var/gerrit/git", "/var/gerrit/index", "/var/gerrit/cache", "/var/gerrit/db", "/var/gerrit/etc"]
ENTRYPOINT ["/bin/bash","/entrypoint.sh"]
As a work directory we use /app directory which is storing in our repository and copy from them an entrypoint.sh script as an entrypoint step in our image. And the RUN
step for installing all necessary tools for succesful running Gerrit application.
There you can see an entrypoint.sh script there
#!/bin/bash -e
export JAVA_OPTS='--add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED'
if [ ! -d /var/gerrit/git/All-Projects.git ] || [ "$1" == "init" ]
then
echo "Initializing Gerrit site ..."
java $JAVA_OPTS -jar /var/gerrit/bin/gerrit.war init --batch --dev -d /var/gerrit
java $JAVA_OPTS -jar /var/gerrit/bin/gerrit.war reindex -d /var/gerrit
git config -f /var/gerrit/etc/gerrit.config --add auth.type "DEVELOPMENT_BECOME_ANY_ACCOUNT"
fi
git config -f /var/gerrit/etc/gerrit.config gerrit.canonicalWebUrl "$CANONICAL_WEB_URL"
if [ "$1" != "init" ]
then
echo "Running Gerrit ..."
exec /var/gerrit/bin/gerrit.sh run
fi
These commands are using for initialization our application and configuring some necessary options. And the final step - run the application.
And the EXPOSE 29418 8080
command are necessary for accessing to our application, 8080 - for WEB UI, 29418 - for SSH actions with application.
And also you can see a volumes
VOLUME ["/var/gerrit/git",
"/var/gerrit/index",
"/var/gerrit/cache",
"/var/gerrit/db",
"/var/gerrit/etc"]
These volumes are needed for storing all application data.
So for more comfortable work with multiple containers we use docker-compose tool. And there you can see a docker-compose.yml
version: "3.7"
services:
bazel_build:
build:
context: ./build
image: bazel_build
volumes:
- bazel_cache:/root/.cache/bazel/_bazel_root
app:
build:
context: ./app
image: application
volumes:
- git:/var/gerrit/git
- etc:/var/gerrit/etc
- db:/var/gerrit/db
- index:/var/gerrit/index
- cache:/var/gerrit/cache
ports:
- 8080:8080
- 29418:29418
volumes:
bazel_cache:
git:
etc:
db:
index:
cache:
So there you can see two services - bazel_build and app, it have all necessary options like working directories, name of future images which will be created using docker-compose build app/bazel_build
command from our Dockerfiles, volumes for storing data, and ports for accessing to application.
P.S. docker-compose command we run in our Jenkins
So for our CI/CD process we using Jenkins and there you can see a Jenkinsfile
pipeline{
agent {label 'arsen'}
environment {
IP_REMOTE_HOST='10.26.0.246'
}
stages {
stage('Test application and build WAR file') {
steps {
sh 'docker-compose -f docker/docker-compose.yml up bazel_test'
sh 'docker container cp docker_bazel_test_1:/app/gerrit2/bazel-bin/release.war docker/app/gerrit.war'
}
}
stage('Build application image') {
steps {
sh 'docker-compose -f docker/docker-compose.yml build app'
sh 'docker save -o application.tar application'
}
}
stage('Deploy application to remote host') {
steps {
sh 'scp application.tar root@${IP_REMOTE_HOST}:/root/application.tar'
sh 'ssh root@${IP_REMOTE_HOST} docker load -i /root/application.tar'
sh 'ssh root@${IP_REMOTE_HOST} docker ps -f name=docker_app_1 -q | xargs --no-run-if-empty docker container stop'
sh 'ssh root@${IP_REMOTE_HOST} docker container ls -a -fname=docker_app_1 -q | xargs -r docker container rm'
sh 'DOCKER_HOST=ssh://root@${IP_REMOTE_HOST} docker-compose -f docker/docker-compose.yml up -d app'
}
}
}
post {
always {
sh 'docker rm docker_bazel_test_1'
cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true)
}
}
}
But for better understanding of commands I transform it to text and show the result of it. Also a small detail that I used docker-agent with installed docker and docker-compose and also I have an another remote host with it too, where I run my application image.