I built a CI with Jenkins for Golang test. We run go test
on a Docker container and even run Jenkins on a Docker container.
Directories
app
├── docker
│ ├── dockerfiles # Dockerfiles for unit test
│ └── test
│ ├── init-db.sh # This initializes DB before testing
│ └── test.sh # Testing script
└── Jenkinsfile # The configuration for Jenkins pipeline
Environment of CI
Our Jenkins server uses an EC2 instance of t2.large, and the server runs on Docker container, and even a unit test run on Docker container on the container Jenkins runs with /var/run/docker.sock
.
Jenkins loads Jenkinsfile and then execute it on the Jenkins pipeline.
How to build an execution environment
Create an AWS EC2 instance
We prepare the instance of EC2 installed Docker CE. Please see Get Docker CE for CentOS installation guide.
Create a Docker image for golang unit test
Jenkins
Dockerfile
FROM jenkins/jenkins:lts
# Switch to root user
USER root
# Install Docker
RUN apt-get update
RUN apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"
RUN apt-get update
RUN apt-get install -y docker-ce
RUN echo "jenkins ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Switch back to jenkins user
USER jenkins
# Set system timezone JST
ENV TZ Asia/Tokyo
Run on a Jenkins host.
$ docker build --rm --tag jenkins-docker:latest .
Golang
Dockerfile
FROM circleci/golang:1.9
# Install goose
RUN curl https://glide.sh/get | sh
RUN go get bitbucket.org/liamstask/goose/cmd/goose
# Set system timezone JST
ENV TZ Asia/Tokyo
Run on our Jenkins host.
$ docker build --rm --tag golang:latest .
Launch Jenkins
$ sudo docker run --env JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -d -p 80:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins-docker:latest
-v /var/run/docker.sock:/var/run/docker.sock
is used to manipulate host's Docker because we want to launch containers on host-side.
-v jenkins_home:/var/jenkins_home
is used to store everything of our Jenkins configurations and build results on our host's filesystem. If you
move your Jenkins to another host or backup your Jenkins data, read this page.
Add a job to Jenkins
We need to enable Jenkins to hook Pull Requests when some developer does it.
Below settings on Jenkins.
Add a credential of GitHub enterprise
Because we use GitHub enterprise our development.
Credentials -> Jenkins -> Global credentials -> Add Credentials
key | value |
---|---|
Kind | Username with password |
Scope | Global |
Username | ci |
Password | **** |
Add our GitHub enterprise
Configure System -> GitHub Enterprise Servers
key | value |
---|---|
API endpoint | http://***/api/v3 |
Name | GitHub Enterprise |
Create a job
New Item -> GitHub Organization -> OK
Configure the job's settings
<Job> -> Configure -> Projects
key | value |
---|---|
API endpoint | GitHub Enterprise (http://***/api/v3) |
Credentials | ci/**** |
Owner | some-repogitory |
Script Path | Jenkinsfile |
<Job> -> Configure -> Projects -> Behaviours
key | value |
---|---|
Filter by name (with regular expression) | some-repogitory |
Discover pull request from forks - Strategy | Merging the pull request with the current target branch revision |
Discover pull request from forks - Trust | Everyone |
Create a webhook in GitHub
To hook PR in Jenkins, We need to create a webhook in GitHub. Note that we must use the user has right permission.
<your repository> -> Settings -> Hooks
key | value |
---|---|
Payload URL | http://***/github-webhook/ |
Content type | application/json |
Which events would you like to trigger this webhook? | Send me everything |
Active | true |
Disable Jenkins' authentication
Because we use Jenkins in a secure place, there are no incoming packets from the internet.
Manage Jenkins -> Configure Global Security -> Access Control -> Authorization -> check Anyone can do anything
Upgrade Jenkins
Since jenkins_home Docker volume has all Jenkins' setting files, We pull a latest Docker image and relaunch Docker container, that's it!
$ sudo docker stop jenkins
$ sudo docker rm jenkins
$ sudo docker pull jenkins/jenkins:lts
$ cd app/docker/dockerfiles/jenkins
$ docker build --rm --tag jenkins-docker:latest .
$ sudo docker run --env JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -d -p 80:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins-docker:latest
Jenkinsfile template
The Jenkinsfile we use, almost same, like this;
pipeline {
agent any
stages {
stage('Checkout') {
steps {
step($class: 'GitHubSetCommitStatusBuilder')
checkout scm
}
}
stage('Start up containers') {
steps {
sh "sudo docker network create ci${env.EXECUTOR_NUMBER}"
sh "sudo docker run -d --name mysql${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} -p 3306${env.EXECUTOR_NUMBER}:3306 circleci/mysql:5.7"
sleep(10)
sh "sudo docker run -d --name redis${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} redis:4.0"
script {
if (sh (
script: "sudo docker create --name golang${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} golang:latest bash /go/src/app/docker/test/test.sh",
returnStatus: true
) == 0) {
sh "sudo docker cp ${env.WORKSPACE} golang${env.EXECUTOR_NUMBER}:/go/src/leo-server"
}
}
}
}
stage('Initialize containers') {
steps {
// Initialize something like DB
}
}
stage('Unit test') {
steps {
script {
if (sh (
script: "sudo docker start -a golang${env.EXECUTOR_NUMBER}",
returnStatus: true
) != 0) {
currentBuild.result = 'FAILURE'
}
}
// Copy test report and convert it into junit xml report
sh "sudo docker cp golang${env.EXECUTOR_NUMBER}:/go/src/app/report.xml ."
step([$class: 'JUnitResultArchiver', testResults: 'report.xml'])
}
}
}
post {
always {
sh script: "sudo docker stop mysql${env.EXECUTOR_NUMBER}", returnStatus: true
sh script: "sudo docker stop redis${env.EXECUTOR_NUMBER}", returnStatus: true
sh script: "sudo docker rm mysql${env.EXECUTOR_NUMBER}", returnStatus: true
sh script: "sudo docker rm redis${env.EXECUTOR_NUMBER}", returnStatus: true
sh script: "sudo docker rm golang${env.EXECUTOR_NUMBER}", returnStatus: true
sh script: "sudo docker network rm ci${env.EXECUTOR_NUMBER}", returnStatus: true
}
}
}
Our test script, test.sh
, like this;
#!/bin/bash
sudo chown -R circleci:circleci /go/src
cd /go/src/leo-server
echo 'Installing go-packages...'
glide i
echo 'Migrating DBs...'
go get bitbucket.org/liamstask/goose/cmd/goose
goose -env=ci -path=database/user up
echo 'Installing testing libraries...'
go get -u github.com/jstemmer/go-junit-report
echo 'Testing...'
go test -v ./... 2>&1 > tmp
status=$?
go-junit-report < tmp > report.xml
exit ${status}