-
Notifications
You must be signed in to change notification settings - Fork 125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi cloud and template automation script #8
Comments
Hi @willis7 I meant to reply but forgot. In general, I package a binary package of a Jenkins server with plugins and publish it somewhere (GitHub releases, artifactory, Nexus, whatever you use). Then using a cloud provisioning tool I would bake images for the cloud (e.g. packer). Then I would provision the infrastructure using those images (terraform or cloudformation) |
I’ll elaborate more later |
It's really great to hear you're doing infra as code, but I was referring to the Docker plugin script 😜 |
Okay that was not clear to me. Ca |
Okay that was not clear to me. Can you elaborate which docker plugin script? There are two. I’ve used these scripts to configure 40 docker clouds in one go so it should be scalable for your needs. The yet-another-docker-plugin script is the one I typically use because the docker plugin was broken using the Docker v1.0 API last I used it. |
This was my attempt at scripting the provisioning of Clouds and Templates in the Docker Plugin. I have since been advised that sharing the same template object over many clouds could be dangerous. I like the use of Groovies config slurper as it gives an appearance of inheritance - and I could seperate that config from the parsing logic. It's all in the same file here. It would be great to get your thoughts. import com.nirima.jenkins.plugins.docker.DockerCloud
import com.nirima.jenkins.plugins.docker.DockerTemplate
import com.nirima.jenkins.plugins.docker.DockerTemplateBase
import com.nirima.jenkins.plugins.docker.launcher.AttachedDockerComputerLauncher
import io.jenkins.docker.connector.DockerComputerAttachConnector
import com.cloudbees.plugins.credentials.Credentials
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.domains.Domain
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
/*
Automatically configure the docker cloud stack in Jenkins.
Docker plugin v1.1.4
Usage:
For adding a new cloud or Template *only* edit rawTemplateConf and/or
rawCloudConf
*/
//
// TEMPLATES
def rawTemplateConf = '''
params {
// Common
pullCredentialsId = 'af-docker-repo__ons-docker-registry'
dnsString = ''
network = ''
dockerCommand = ''
volumesString = ''
volumesFromString = ''
environmentsString =''
hostname = ''
memoryLimit = null
memorySwap = null
cpuShares = null
bindPorts = ''
bindAllPorts = false
privileged = false
tty = true
macAddress = ''
extraHostsString = ''
instanceCapStr = '4'
remoteFs = ''
}
templates {
'jenkins-slave-base' {}
'scala_2-11-8' {}
'node_6-11-5' {}
'node_9.9.0' {}
'maven_3.2.5' {}
'maven_3.5.4' {}
'python_2.7.15' {}
'python_3.3.0' {}
'r_3.5.0-1' {}
'sbt_0-13-13' {}
}
environments {
'jenkins-slave-base' {
params {
labelString = 'jenkins.slave'
image = 'onsdigital/jenkins-slave-base'
}
}
'scala_2-11-8' {
params {
labelString = 'scala_2-11-8'
image = 'onsdigital/jenkins-slave-scala:2.11.8'
}
}
'node_6-11-5' {
params {
labelString = 'node_6-11-5'
image = 'onsdigital/jenkins-slave-node:v6.11.5'
}
}
'node_9.9.0' {
params {
labelString = 'node_9.9.0'
image = 'onsdigital/jenkins-slave-node:v9.9.0'
}
}
'maven_3.2.5' {
params {
labelString = 'maven_3.2.5'
image = 'onsdigital/jenkins-slave-maven:3.2.5'
}
}
'maven_3.5.4' {
params {
labelString = 'maven_3.5.4'
image = 'onsdigital/jenkins-slave-maven:3.5.4'
}
}
'python_2.7.15' {
params {
labelString = 'python_2.7.15'
image = 'onsdigital/jenkins-slave-python:2.7.15'
}
}
'python_3.3.0' {
params {
labelString = 'python_3.3.0'
image = 'onsdigital/jenkins-slave-python:3.3.0'
}
}
'r_3.5.0-1' {
params {
labelString = 'r_3.5.0-1'
image = 'onsdigital/jenkins-slave-r:3.5.0-1'
}
}
'sbt_0-13-13' {
params {
labelString = 'sbt_0-13-13'
image = 'onsdigital/jenkins-slave-sbt:0.13.13'
}
}
}
'''
//
// CLOUDS
def rawCloudConf = '''
params{
containerCapStr = '10'
connectTimeout = 5
credentialsId = ''
dockerHostname = ''
readTimeout = 15
version = ''
}
clouds {
download {}
build {}
test {}
deploy{}
}
environments {
download{
params{
name = 'download'
serverUrl = 'tcp://10.50.34.32:4243'
templates = ['jenkins-slave-base']
}
}
build{
params{
name = 'build'
serverUrl = 'tcp://10.50.34.39:4243'
templates = ['jenkins-slave-base',
'scala_2-11-8',
'node_6-11-5',
'node_9.9.0',
'maven_3.2.5',
'maven_3.5.4',
'python_2.7.15',
'python_3.3.0',
'r_3.5.0-1',
'sbt_0-13-13']
}
}
test{
params{
name = 'test'
serverUrl = 'tcp://10.50.34.54:4243'
templates = ['jenkins-slave-base',
'scala_2-11-8',
'node_6-11-5',
'node_9.9.0',
'maven_3.2.5',
'maven_3.5.4',
'python_2.7.15',
'python_3.3.0',
'r_3.5.0-1',
'sbt_0-13-13']
}
}
deploy{
params{
name = 'deploy'
serverUrl = 'tcp://10.50.34.69:4243'
templates = ['jenkins-slave-base']
}
}
}
'''
//configure credentials
SystemCredentialsProvider system_creds = SystemCredentialsProvider.getInstance()
Boolean foundId=false
system_creds.getCredentials().each{
if('af-docker-repo__ons-docker-registry'.equals(it.getId())) {
foundId=true
}
}
// +--------------------+
// | |
// | DockerTemplateBase |
// | |
// +--------------------+
// |
// |
// +-------v--------+
// | |
// | DockerTemplate |
// | |
// +----------------+
// |
// |
// +------v------+
// | |
// | DockerCloud |
// | |
// +-------------+
//
// Docker Plugin API Class Diagram
// Constructor
def newDockerTemplateBase(Map templateParams) {
// https://github.com/jenkinsci/docker-plugin/blob/docker-plugin-1.1.2/src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java
new DockerTemplateBase(
templateParams.image,
templateParams.pullCredentialsId,
templateParams.dnsString,
templateParams.network,
templateParams.dockerCommand,
templateParams.volumesString,
templateParams.volumesFromString,
templateParams.environmentsString,
templateParams.hostname,
templateParams.memoryLimit,
templateParams.memorySwap,
templateParams.cpuShares,
templateParams.bindPorts,
templateParams.bindAllPorts,
templateParams.privileged,
templateParams.tty,
templateParams.macAddress,
templateParams.extraHostsString
)
}
// Constructor
def newDockerTemplate(DockerTemplateBase dockerTemplateBase, Map templateParams) {
// https://github.com/jenkinsci/docker-plugin/blob/docker-plugin-1.1.2/src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplate.java
new DockerTemplate(
dockerTemplateBase,
new DockerComputerAttachConnector(),
templateParams.labelString,
templateParams.remoteFs,
templateParams.instanceCapStr
)
}
// Constructor
def newDockerCloud(def cloudParams, def templates) {
// https://github.com/jenkinsci/docker-plugin/blob/docker-plugin-1.1.2/src/main/java/com/nirima/jenkins/plugins/docker/DockerCloud.java
new DockerCloud(
cloudParams.name,
templates,
cloudParams.serverUrl,
cloudParams.containerCapStr,
cloudParams.connectTimeout,
cloudParams.readTimeout,
cloudParams.credentialsId,
cloudParams.version,
cloudParams.dockerHostname
)
}
// buildTemplates returns a Map whose key matches the image name
// and the value is a DockerTemplate object. The function first creates the DockerTemplateBase
// object and uses that to construct the DockerTemplate. See class diagram above.
def buildTemplates(def templateConfig) {
def config = new ConfigSlurper().parse(templateConfig)
def templates = [:]
config.templates.each { name, unused ->
namedTemplateConf = new ConfigSlurper(name).parse(templateConfig)
dockerTemplateBase = newDockerTemplateBase(namedTemplateConf.params)
dockerTemplate = newDockerTemplate(dockerTemplateBase, namedTemplateConf.params)
templates.put(name, dockerTemplate)
}
return templates
}
// buildClouds returns a List of DockerCloud objects with wiring to
// the templates which should linked to this cloud.
def buildClouds(def cloudConfig, Map templates) {
def config = new ConfigSlurper().parse(cloudConfig)
def clouds = []
config.clouds.each { name, unused ->
namedCloudConf = new ConfigSlurper(name).parse(cloudConfig)
def attachTmpl = []
namedCloudConf.params.templates.each {
attachTmpl << templates.get(it)
}
clouds << newDockerCloud(namedCloudConf.params, attachTmpl)
}
return clouds
}
// buildAll creates all the API objects from the raw config
// and returns a list of clouds
def buildAll(def templateConfig, def cloudConfig) {
templates = buildTemplates(templateConfig)
clouds = buildClouds(cloudConfig, templates)
return clouds
}
Jenkins jenkins = Jenkins.getInstance()
// remove existing clouds
jenkins.clouds.removeAll(DockerCloud.class)
// install new clouds
jenkins.clouds.addAll(buildAll(rawTemplateConf, rawCloudConf))
// save current Jenkins state to disk
jenkins.save()
println "If there are no error messages above ^^^ then I guess I ran successfully...." |
I wouldn’t recommend sharing the same template base across many clouds. In general, that’s not how configuring via the web ui would instantiate objects. Jenkins would use a new instance per cloud. Anything other than that may have unintended side effects or bugs since the runtime doesn’t normally encounter that from web ui setup. Have you seen my docker-plugin script which includes an example JSON config? It includes sane defaults when settings are omitted from the JSON map. |
I had seen it, but I couldnt see how I could avoid scaling hell (extra lines of code for almost identical templates) which led to my solution. With hindsight, I would have just used your script, as the dangers in my script make it too risky to use. |
In my script you can templates the JSON object. The JSON object is read for values and not directly creating anything. So you can templatize as many settings as you want. The important part is that there are new instances of the docker classes. |
Also note, that my config includes every setting but does not have to. Take for example this line
If you don’t specify the setting at all in the JSONObject, then it will default to false. Learn more about net.sf.json.JSONObject So you can customize as many defaults as you want in that script to limit your need to templatize. And then rely on JSONObject templates when you do need it. net.sf.json.JSONObject is a class built into Jenkins core. |
I have the same behavior in my script. Anything in common can be overwritten per template. That works really well. I had tried your script as I mentioned before but was blocked by version compatibility - I was using a newer version of Docker Plugin and the API had changed. My question was more; can I have the same template conf (albeit not the same instance of DockerTemplate) per cloud without duplication, and I believe the answer is no. |
I don’t see why you couldn’t have the same template conf. As long as you’re not sharing docker class instances it should be fine. |
I might try to update that script this week. I don’t normally use that plugin because for a time it was abandoned and didn’t work for the latest docker API. Times have changed I guess. |
I would really find that useful and would happily reuse. |
Hey, I recently watched your talk about using the script console. Great!
I have a use case where I need to add 4 clouds, and multiple templates. The clouds will be linked to some or all of the templates. From your scripts I can see you doing a 1-2-1 (template-2-cloud) mapping, but I envisage this isn't scaleable for my scenario.
Have you tried this? Can you steer me in the right direction?
The text was updated successfully, but these errors were encountered: