Commit 23279188 authored by qhoangxuan's avatar qhoangxuan

ci folder + document

parent 738b1811
# Zoe Continuous Integration
----------------------------
# Overview
- Integrated Zoe repository to Jenkins and SonarQube
- Each commit to Zoe repository trigger a build at Jenkins:
- Run SonarQube Scanner to analyze the codebase
- Create two containers for zoe-master, zoe-api
- Run integration test [testing rest api]
- Build new images if no errors happen
- Deploy Zoe with latest images
# Software Stack
- Jenkins - version 2.7.4
- SonarQube - version 9.6.0
# Configuration
### Jenkins
- Required:
- Plugins: Github plugin, SonarQube Plugin, Email Plugin (optional)
- Softwares: Java, Python, Docker
- Go to Manage Jenkins, then Global Tool Configuration to setup Java SDK
- SonarQube server configuration:
- Go to Manage Jenkins then Configure System
- SonarQube servers: input name, server URL, server version, **sever authentication token** (created on SonarQube Server)
- Create credentials for Github account
- Create SSH key pair on Jenkins Server
- Add public key to Github
- Add private key to Jenkins credentials
- Create new item as a **freestyle project** to descibe Github repository
- General
- Select Github project
- Insert project URL
- Source Code Management
- Select **Git**
- Repositories
- Repository URL: use **SSH URL** of github repository
- Credentials: select the one created above
- Build Triggers
- Select **Build when a change is pushed to Github**
- Post-build Actions [Optional]
- Add **E-mail Notification** for notifying when jobs finish
### Github
- Add new SSH key (the one created on Jenkins server)
- Go to the project (which is integrated to Jenkins) settings
- Integration & Services
- Add Service, choose **Jenkins (Github plugin)**
- Add Jenkins hook url
- For github plugin, this one would have the format: http://your-jenkins.com/github-webhook/
- In case your Jenkins doens't expose to the world, try **ngrok**
### SonarQube
- On **Administration**, go to **My Account**, then **Security**
- Generate Tokens, copy this and paste to **server authentication token** on Jenkins configuration
- The project needs to provides **sonar-properties** file in the repo: http://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner
#!/usr/bin/env python3
# Copyright (c) 2016, Quang-Nhat Hoang-Xuan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Backend Deploy."""
import logging
import time
import yaml
from typing import Iterable, Callable, Dict, Any, Union
import docker
from docker import Client
from utils.DockerContainerParameter import DockerContainerParameter
class ZoeBackendDeploy():
def __init__(self, dockerUrl, dockerComposePath):
self.cli = Client(base_url=dockerUrl)
self.zoe_api = ''
self.zoe_master = ''
self.zoe_opts = []
self.docker_compose_file = dockerComposePath
self.typeDeploy = 1 if 'prod' in dockerComposePath else 0
self.parse_docker_compose()
self.previousImage=''
def parse_docker_compose(self):
content = ''
with open(self.docker_compose_file, 'r') as stream:
content = list(yaml.load(stream)['services'].items())
for i in range(0,2):
opts = DockerContainerParameter()
if 'api' in content[i][0]:
self.zoe_api = content[i][0]
else:
self.zoe_master = content[i][0]
opts.set_name(content[i][0])
opts.set_hostname(content[i][0])
details = content[i][1]
opts.set_image(details['image'])
opts.set_command(details['command'])
if 'volumes' in details:
opts.set_volumes(details['volumes'])
if 'ports' in details:
opts.set_ports(details['ports'])
if 'logging' in details:
logging = details['logging']
if logging['driver'] == 'gelf':
opts.set_gelf(logging['options']['gelf-address'])
self.zoe_opts.append(opts)
def create_container(self, opts, image):
port_binds = {}
volume_binds = {}
ports = []
volumes = []
opts.set_image(image)
if len(opts.get_ports()) != 0:
ports_list = opts.get_ports()[0]
for p in ports_list:
splitted = p.split(":")
port_binds[int(splitted[0])] = int(splitted[1])
ports.append(int(splitted[0]))
if len(opts.get_volumes()) != 0:
volumes_list = opts.get_volumes()[0]
for v in volumes_list:
splitted = v.split(":")
bind = {'bind' : splitted[1], 'mode': 'rw'}
volume_binds[splitted[0]] = bind
volumes.append(splitted[1])
if opts.get_gelf() != '':
log_config = docker.utils.LogConfig(type = 'gelf', config = {'gelf-address': opts.get_gelf()})
host_config = self.cli.create_host_config(
network_mode='bridge',
port_bindings= port_binds,
binds= volume_binds,
log_config = log_config)
ctn = self.cli.create_container(
image = opts.get_image(),
command = opts.get_command(),
hostname = opts.get_hostname(),
name = opts.get_name(),
tty=True,
ports=ports,
volumes=volumes,
host_config=host_config)
print('Created ' + opts.get_name() + ' container')
return ctn
def export_master_ip(self):
try:
ip_zoe_master = self.cli.inspect_container(self.zoe_master)['NetworkSettings']['Networks']['bridge']['IPAddress']
hostEntry = ip_zoe_master + '\t' + self.zoe_master
add_to_host = 'bash -c "echo ' + "'" + hostEntry + "'" + ' >> /etc/hosts"'
id = self.cli.exec_create(self.zoe_api, add_to_host)
self.cli.exec_start(id)
except Exception as ex:
print(ex)
def deploy(self, image):
'''deploy with docker-compose, if success, return, else, fallback '''
try:
retcode = 1
for s in [self.zoe_api, self.zoe_master]:
res = self.cli.containers(all=True, filters={'name': s})
if len(res) > 0:
for r in res:
name = r['Names'][0].split("/")[1]
if self.typeDeploy == 0:
if name == s:
self.cli.remove_container(name, force=True)
else:
if name == s:
imgID = self.cli.inspect_container(s)['Image']
self.previousImage = self.cli.inspect_image(imgID)['RepoTags'][0]
self.cli.remove_container(name, force=True)
print('Removed ' + name + ' container')
print('deploying with ' + image)
for opts in self.zoe_opts:
ctn = self.create_container(opts, image)
print('Deploying zoe backend...')
#start zoe_api
self.cli.start(self.zoe_api)
print('Started latest ' + self.zoe_api + ' container...')
if not self.cli.inspect_container(self.zoe_api)['State']['Running']:
retcode = -1
time.sleep(5)
#start zoe_master
self.cli.start(self.zoe_master)
print('Started latest ' + self.zoe_master + ' container...')
if not self.cli.inspect_container(self.zoe_master)['State']['Running']:
retcode = 0
#export zoe-master ip address to hosts file of zoe-api
self.export_master_ip()
except Exception as ex:
print(ex)
retcode = 0
pass
return retcode
def fallback(self, image):
return
#!/usr/bin/env python3
# Copyright (c) 2016, Quang-Nhat Hoang-Xuan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Frontend deploy."""
from docker import Client
import sys
class ZoeFrontendDeploy():
def __init__(self, dockerUrl, apache2):
self.src = 'prod.tar'
self.srcBackup = 'backup.tar'
self.dst = '/var/www/'
self.dstBackup = '/var/www/prod'
self.cli = Client(base_url=dockerUrl)
self.apache2 = apache2
return
def deploy(self):
""" Put new frontend folder behind apache2 container """
try:
retcode = 1
#do backup
strm, stat = self.cli.get_archive(container=self.apache2, path=self.dstBackup)
filebackup = open(self.srcBackup, 'wb')
filebackup.write(strm.read())
filedata = open(self.src, 'rb').read()
res = self.cli.put_archive(container=self.apache2, path=self.dst, data=filedata)
if res is False:
retcode = 0
except Exception as ex:
print(ex)
retcode = 0
return retcode
def fallback(self):
try:
retcode = 1
filebackup = open(self.srcBackup, 'rb').read()
res = self.cli.put_archive(container=self.apache2, path=self.dst, data=filebackup)
if res is False:
retcode = 0
except Exception as ex:
print(ex)
retcode = 0
return retcode
version: '2'
services:
zoe-api:
image: 192.168.12.2:5000/zoe:reducetime
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: python3 zoe-api.py --debug --swarm consul://192.168.12.2 --deployment-name prod --dbuser postgres --dbhost 192.168.12.2 --dbport 5432 --dbname postgres --dbpass postgres --overlay-network-name my-net --master-url tcp://zoe-master:4850 --auth-type ldap --ldap-server-uri ldap://172.17.0.6 --ldap-base-dn ou=users,dc=example,dc=com --proxy-type apache --proxy-container apache2 --proxy-config-file /etc/apache2/sites-available/all.conf --proxy-path fsdna.on.kpmg.de/zoe --gelf-address udp://192.168.12.2:5004
ports:
- "5001:5001"
logging:
driver: "gelf"
options:
gelf-address: "udp://192.168.12.2:5004"
tag: "zoe-api"
zoe-master:
image: 192.168.12.2:5000/zoe:reducetime
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: python3 zoe-master.py --debug --swarm consul://192.168.12.2 --deployment-name prod --dbuser postgres --dbhost 192.168.12.2 --dbport 5432 --dbname postgres --dbpass postgres --overlay-network-name my-net --auth-type ldap --ldap-server-uri ldap://ldapker.example.com --ldap-base-dn ou=users,dc=example,dc=com --proxy-type apache --proxy-container apache2 --proxy-config-file /etc/apache2/sites-available/all.conf --proxy-path fsdna.on.kpmg.de/zoe --gelf-address udp://192.168.12.2:5004
ports:
- "4850:4850"
depends_on:
- zoe-api
logging:
driver: "gelf"
options:
gelf-address: "udp://192.168.12.2:5004"
tag: "zoe-api"
version: '2'
services:
zoe-api-test:
image: 192.168.12.2:5000/zoe:reducetime
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: python3 zoe-api.py --debug --swarm consul://192.168.12.2 --deployment-name test --dbuser postgres --dbhost 192.168.12.2 --dbport 4321 --dbname postgres --dbpass postgres --overlay-network-name my-net --master-url tcp://zoe-master-test:4850 --auth-type text --proxy-type apache --proxy-container apache2 --proxy-config-file /etc/apache2/sites-available/all.conf --proxy-path fsdna.on.kpmg.de/zoe --listen-port 5100 --gelf-address udp://192.168.12.2:5004
ports:
- "5100:5100"
logging:
driver: "gelf"
options:
gelf-address: "udp://192.168.12.2:5004"
tag: "zoe-api"
zoe-master-test:
image: 192.168.12.2:5000/zoe:reducetime
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: python3 zoe-master.py --debug --swarm consul://192.168.12.2 --deployment-name test --dbuser postgres --dbhost 192.168.12.2 --dbport 4321 --dbname postgres --dbpass postgres --overlay-network-name my-net --auth-type text --proxy-type apache --proxy-container apache2 --proxy-config-file /etc/apache2/sites-available/all.conf --proxy-path fsdna.on.kpmg.de/zoe --listen-port 5100 --gelf-address udp://192.168.12.2:5004 --gelf-address udp://192.168.12.2:5004
depends_on:
- zoe-api
logging:
driver: "gelf"
options:
gelf-address: "udp://192.168.12.2:5004"
tag: "zoe-master"
#!/usr/bin/env python3
# Copyright (c) 2016, Quang-Nhat Hoang-Xuan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Container Parameter class"""
from typing import Iterable, Callable, Dict, Any, Union
class DockerContainerParameter():
def __init__(self):
self.image = ''
self.volumes = []
self.command = ""
self.name = ''
self.ports = []
self.network_name = 'bridge'
self.gelf_address = ''
self.restart = True
self.hostname = ''
def set_gelf(self, gelf_address):
self.gelf_address = gelf_address
def get_gelf(self) -> str:
return self.gelf_address
def set_ports(self, ports):
self.ports.append(ports)
def get_ports(self) -> Iterable[str]:
return self.ports
def set_image(self, image) -> str:
self.image = image
def get_image(self) -> str:
return self.image
def set_volumes(self, volumes):
self.volumes.append(volumes)
def get_volumes(self) -> Iterable[str]:
"""Get the volumes in Docker format."""
return self.volumes
def set_command(self, cmd) -> str:
"""Setter for the command to run in the container."""
self.command = cmd
def get_command(self) -> str:
"""Getter for the command to run in the container."""
return self.command
def set_name(self, name) -> str:
self.name = name
def get_name(self) -> str:
return self.name
def set_hostname(self, hostname) -> str:
self.hostname = hostname
def get_hostname(self) -> str:
return self.hostname
#!/usr/bin/env python3
# Copyright (c) 2016, Quang-Nhat Hoang-Xuan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""ZOE CI entry point."""
import yaml
import sys
from typing import Iterable, Callable, Dict, Any, Union
import docker
from docker import Client
from utils.DockerContainerParameter import DockerContainerParameter
from deploy.frontenddeploy import ZoeFrontendDeploy
from deploy.backenddeploy import ZoeBackendDeploy
class ZoeDeploy():
def __init__(self, dockerUrl, dockerComposePath, image):
self.currentImage = image
self.typeDeploy = 1 if 'prod' in dockerComposePath else 0
self.backend = ZoeBackendDeploy(dockerUrl, dockerComposePath)
self.frontend = ZoeFrontendDeploy(dockerUrl, 'apache2')
def deploy(self):
""" Deploy zoe backend and frontend """
try:
retBE = self.backend.deploy(self.currentImage)
print('Deployed BE with latest image...')
if self.typeDeploy == 1 and retBE == 0:
print('Redeploy BE with previous image')
self.backend.deploy(self.backend.previousImage)
retFE = 1
if self.typeDeploy == 1:
retFE = self.frontend.deploy()
print('Deployed FE with latest codes...')
if retFE == 0 or retBE == 0:
retFE = self.frontend.fallback()
except Exception as ex:
print(ex)
retBE = 0
return (retBE and retFE)
class ZoeImage():
def __init__(self, dockerUrl, tag):
self.cli = Client(base_url=dockerUrl)
self.tag = tag
def build(self):
""" Build docker image """
ret = 1
for line in self.cli.build(path='.', tag=self.tag, rm=True):
print(line)
if 'error' in str(line):
ret = 0
return ret
def push(self):
""" Push docker image """
ret = 1
for line in self.cli.push(self.tag, stream=True):
print(line)
if 'error' in str(line):
ret = 0
return ret
if __name__ == '__main__':
if len(sys.argv) < 4:
sys.exit(1)
else:
if sys.argv[1] == '0':
deployer = ZoeDeploy(sys.argv[2], sys.argv[3], sys.argv[4])
ret = deployer.deploy()
if ret == 0:
sys.exit(1)
elif sys.argv[1] == '1':
imghandler = ZoeImage(sys.argv[2], sys.argv[3])
ret = imghandler.build()
if ret == 0:
sys.exit(1)
elif sys.argv[1] == '2':
imghandler = ZoeImage(sys.argv[2], sys.argv[3])
ret = imghandler.push()
if ret == 0:
sys.exit(1)
sonar.projectKey=de.kpmg.on.fsdna.zoe
sonar.projectName=Zoe-KPMG
sonar.projectVersion=1.0
sonar.host.url=http://fsdna.on.kpmg.de/sonar
sonar.sources=.
sonar.modules=zoe_api,zoe_master,zoe_lib,zoe_fe
sonar.sourceEncoding=UTF-8
sonar.projectName=zoe_api
sonar.host.url=http://fsdna.on.kpmg.de/sonar
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
sonar.projectName=zoe_fe
sonar.projectVerion = 1.0
sonar.host.url=http://fsdna.on.kpmg.de/sonar
sonar.sources=src
sonar.exclusions=bower_components/**/*, node_modules/**/*
sonar.sourceEncoding=UTF-8
sonar.projectName=zoe_lib
sonar.host.url=http://fsdna.on.kpmg.de/sonar
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
sonar.projectName=zoe_master
sonar.host.url=http://fsdna.on.kpmg.de/sonar
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment