Docker: AWS [China] – Jenkins в Docker

Автор: | 26/06/2017

Продолжение развёртывания CI инфрастуктуры в Китае. Начало тут>>>.

В предыдущей части – запустили Docker registry, теперь – нужен Jenkins, который будет собирать образы сервисов и пушить в это хранилище.

Для запуска Jenkins потребуется:

  1. создать EBS для Jenkins workspaces и подключить к EC2
  2. создать EBS для Docker образов
  3. запустить и проверить Jenkins
  4. проверить Docker билды в Jenkins
  5. подготовить Docker Compose файл для его запуска в дальнейшем

Подготовка EC2 и EBS для Jenkins

EC2 уже есть. Добавляем к нему Elastic Block Storage, в той же Availability Zone, что и EC2:

[simterm]

$ aws ec2 describe-instances --instance-ids i-01edb17886a20b25d --query 'Reservations[*].Instances[*].Placement.AvailabilityZone' --output text
cn-north-1b

[/simterm]

Создаём EBS:

[simterm]

$ aws ec2 create-volume --size 80 --region cn-north-1 --availability-zone cn-north-1b --volume-type gp2
{
    "AvailabilityZone": "cn-north-1b", 
    "Encrypted": false, 
    "VolumeType": "gp2", 
    "VolumeId": "vol-0867c0e01f2ddf30b", 
    "State": "creating", 
    "Iops": 240, 
    "SnapshotId": "", 
    "CreateTime": "2017-06-22T11:26:31.289Z", 
    "Size": 80
}

[/simterm]

Добавляем теги:

[simterm]

$ aws ec2 create-tags --resources vol-0867c0e01f2ddf30b --tags Key=Name,Value=tag-cn-jenkins-workspaces

[/simterm]

Подключаем его к EC2.

Проверяем имеющиеся разделы:

[simterm]

$ lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /

[/simterm]

Подключаем:

[simterm]

$ aws ec2 attach-volume --volume-id vol-0867c0e01f2ddf30b --instance-id  i-01edb17886a20b25d --device /dev/xvdb
{
    "AttachTime": "2017-06-22T11:28:16.640Z", 
    "InstanceId": "i-01edb17886a20b25d", 
    "VolumeId": "vol-0867c0e01f2ddf30b", 
    "State": "attaching", 
    "Device": "/dev/xvdb"
}

[/simterm]

На интансе проверяем:

[simterm]

$ lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdb    202:16   0  80G  0 disk 

[/simterm]

Создаём новый раздел на устройстве xvdb:

[simterm]

$ sudo fdisk /dev/xvdb

Welcome to fdisk (util-linux 2.27.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xe23e34c2.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): 

Using default response p.
Partition number (1-4, default 1): 
First sector (2048-167772159, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-167772159, default 167772159): 

Created a new partition 1 of type 'Linux' and of size 80 GiB.

Command (m for help): p
Disk /dev/xvdb: 80 GiB, 85899345920 bytes, 167772160 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe23e34c2

Device     Boot Start       End   Sectors Size Id Type
/dev/xvdb1       2048 167772159 167770112  80G 83 Linux

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

[/simterm]

Проверяем:

[simterm]

$ lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdb    202:16   0  80G  0 disk 
└─xvdb1 202:17   0  80G  0 part 

[/simterm]

Создаём файловую систему:

[simterm]

$ sudo mkfs.ext4 /dev/xvdb1

[/simterm]

Создаём точку монтирования, монтируем раздел:

[simterm]

$ sudo mkdir /jenkins
$ sudo mount /dev/xvdb1 /jenkins/
$ ls -l /jenkins/
total 16
drwx------ 2 root root 16384 Jun 22 11:33 lost+found

[/simterm]

Обновляем /etc/fstab, добавляем монтирование при старте машины.

Находим UUID раздела:

[simterm]

$ sudo blkid /dev/xvdb1
/dev/xvdb1: UUID="9d0efa97-c292-479d-b2e3-e83a3ab8d40f" TYPE="ext4" PARTUUID="e23e34c2-01"

[/simterm]

Редактируем /etc/fstab (с nofail опцией):

[simterm]

$ cat /etc/fstab 
LABEL=cloudimg-rootfs   /        ext4   defaults,discard        0 0
UUID=9d0efa97-c292-479d-b2e3-e83a3ab8d40f /jenkins ext4 defaults,nofail 0 2

[/simterm]

Готово.

EBS для Docker образов

Немного забегая наперёд – сразу добавим раздел для образов Docker.

Создаём EBS аналогично разделу для Jenkins:

[simterm]

$ aws ec2 create-volume --size 80 --region cn-north-1 --availability-zone cn-north-1b --volume-type gp2
$ aws ec2 create-tags --resources vol-0f756a2781a4cdc40 --tags Key=Name,Value=tag-cn-docker-lib
$ aws ec2 attach-volume --volume-id vol-0f756a2781a4cdc40 --instance-id  i-01edb17886a20b25d --device /dev/xvdc
$ echo ';' | sudo sfdisk /dev/xvdc
$ sudo mkfs.ext4 /dev/xvdc1
$ sudo mkdir /docker
$ sudo mount /dev/xvdc1 /docker/
$ ls -l /docker/
total 16
drwx------ 2 root root 16384 Jun 22 13:40 lost+found
$ lsblk 
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0    8G  0 disk 
└─xvda1 202:1    0    8G  0 part /
xvdb    202:16   0   80G  0 disk 
└─xvdb1 202:17   0   80G  0 part /jenkins
xvdc    202:32   0  100G  0 disk 
└─xvdc1 202:33   0  100G  0 part /docker

[/simterm]

Создаём файл для настройки Docker engine:

[simterm]

$ sudo vim /etc/docker/daemon.json

[/simterm]

В котором указываем каталог:

{
  "graph": "/docker/"
}

Перезапускаем docker:

[simterm]

$ sudo service docker restart

[/simterm]

Проверяем:

[simterm]

$ sudo ls -l /docker/
total 52
drwx------ 5 root root  4096 Jun 22 13:44 aufs
drwx------ 2 root root  4096 Jun 22 13:44 containers
drwx------ 3 root root  4096 Jun 22 13:44 image
drwx------ 2 root root 16384 Jun 22 13:40 lost+found
drwxr-x--- 3 root root  4096 Jun 22 13:44 network
drwx------ 4 root root  4096 Jun 22 13:44 plugins
drwx------ 2 root root  4096 Jun 22 13:44 swarm
drwx------ 2 root root  4096 Jun 22 13:44 tmp
drwx------ 2 root root  4096 Jun 22 13:44 trust
drwx------ 2 root root  4096 Jun 22 13:44 volumes

[/simterm]

Запуск Jenkins

Сначала запустим его вручную, через docker run, потом – добавим Docker Compose файл.

Т.к. это Китай со своими весёлостями в плане скорости – сначала сделем просто pull (через китайское зеркало DockerHub pull вообще не прошёл, падал на одном и том же слое с ошибкой 500):

[simterm]

$ time docker pull jenkins                                                                                                                                               
Using default tag: latest                                                                                                                                                                                                                     
latest: Pulling from library/jenkins                                                                                                                                                                                                          
...
Digest: sha256:1a017bc762d8177347dbccbe258de842015114806110db060d91cff7e8a3264f
Status: Downloaded newer image for jenkins:latest

real    137m43.960s

[/simterm]

Пробуем запустить:

[simterm]

$ docker run -ti -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' jenkins
mkdir: cannot create directory ‘/jenkins/init.groovy.d’: Permission denied
cp: cannot create regular file ‘/jenkins/init.groovy.d/tcp-slave-agent-port.groovy’: No such file or directory
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
Jun 26, 2017 10:40:44 AM Main deleteWinstoneTempContents
WARNING: Failed to delete the temporary Winstone file /tmp/winstone/jenkins.war
Jun 26, 2017 10:40:44 AM org.eclipse.jetty.util.log.JavaUtilLog info
INFO: Logging initialized @595ms
Jun 26, 2017 10:40:44 AM winstone.Logger logInternal
INFO: Beginning extraction from war file
Jun 26, 2017 10:40:44 AM winstone.Logger logInternal
INFO: Winstone shutdown successfully
Jun 26, 2017 10:40:44 AM winstone.Logger logInternal
SEVERE: Container startup failed
java.io.FileNotFoundException: /jenkins/war/META-INF/MANIFEST.MF (No such file or directory)
...

[/simterm]

Проверяем пользователя каталога /jenkins:

[simterm]

$ ls -l / | grep jenk
drwxr-xr-x   3 root root  4096 Jun 26 10:39 jenkins

[/simterm]

Проверяем пользователя в контейнере:

[simterm]

# docker run -u jenkins jenkins id
uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)

[/simterm]

Находим пользователя с UID 1000 на хосте:

[simterm]

# cat /etc/passwd | grep 1000
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash

[/simterm]

Меняем владельца каталога /jenkins и запускаем:

[simterm]

$ sudo chown ubuntu:ubuntu /jenkins/
$ docker run -ti -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' jenkins
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
Jun 26, 2017 10:43:32 AM Main deleteWinstoneTempContents
WARNING: Failed to delete the temporary Winstone file /tmp/winstone/jenkins.war                                                                                                                                                               
Jun 26, 2017 10:43:32 AM org.eclipse.jetty.util.log.JavaUtilLog info
INFO: Logging initialized @587ms
Jun 26, 2017 10:43:32 AM winstone.Logger logInternal
INFO: Beginning extraction from war file
...

[/simterm]

Отлично – Jenkins стартует.

Docker билд в Jenkins

Что бы использовать Docker в Jenkins-контейнере – можно замапить исполняемый файл докера в сам контейнер:

[simterm]

$ docker run -ti -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker:ro -e JENKINS_HOME=’/jenkins’ jenkins

[/simterm]

Но так могут вылезти ошибки вроде такой:

docker: error while loading shared libraries: libltdl.so.7: cannot open shared object file: No such file or directory

Или возможны проблемы с доступом к /var/run/docker/sock:

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.29/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&shmsize=0&t=registry.domain.cn%3A5000%2Fubuntu%3A20&target=&ulimits=null: dial unix /var/run/docker.sock: connect: permission denied

Другой подход – собрать свой образ Jenkins с установленным в нём Docker.

Создаём Dockerfile:

FROM jenkins
 
USER root
RUN curl https://get.docker.com/ | bash
RUN usermod -aG docker jenkins

USER jenkins

Собираем:

[simterm]

$ docker build -t testjenk:1 .

[/simterm]

Запускаем:

[simterm]

$ docker run -ti -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' testjenk:1 bash

[/simterm]

Проверяем билд в самом контейнере:

[simterm]

$ cd /jenkins/workspace/DockerTestJob
$ docker build -t asdcsdc .
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM ubuntu
latest: Pulling from library/ubuntu
75c416ea735c: Downloading [>                                                  ]  471.9kB/47.1MB
...

[/simterm]

Работает.

Проверка Docker билда в Jenkins

Запускаем Jenkins контейнер:

[simterm]

$ docker run -ti -p 80:8080 -v /jenkins/:/jenkins -v /var/run/docker.sock:/var/run/docker.sock -e JENKINS_HOME='/jenkins' testjenk:1

[/simterm]

Создаём тестовую джобу:

Создаём скрипт:

def dockerBuild () {

    stage ('Docker build') {

        def appimage = docker.build("ubuntu:${env.BUILD_NUMBER}")
    }
}

node {
    dockerBuild()
}

Запускаем:

ОК – билд работает.

Билд и пуш в Docker registry

Следующим шагом – проверяем работу Jenkins и Docker registry из предыдущей части.

В Credentials – добавляем данные доступа к созданному ранее registry:

Обновляем билд скрипт:

def dockerBuild () {

    stage ('Docker build') {

        docker.withRegistry('https://registry.domain.cn:5000', 'tagcndockerregistry'){      
            def img = docker.build("ubuntu:${env.BUILD_NUMBER}")
                img.push()
        }
    }
}

node {
    
    dockerBuild()
}

Запускаем билд, проверяем:


[DockerTestJob] Running shell script
+ docker build -t registry.domain.cn:5000/ubuntu:15 .
Sending build context to Docker daemon 2.048kB

Step 1/1 : FROM ubuntu
—> d355ed3537e9
Successfully built d355ed3537e9
Successfully tagged registry.domain.cn:5000/ubuntu:15
[Pipeline] dockerFingerprintFrom
[Pipeline] sh
[DockerTestJob] Running shell script
+ docker tag –force=true registry.domain.cn:5000/ubuntu:15 registry.domain.cn:5000/ubuntu:15
unknown flag: –force
See ‘docker tag –help’.
+ docker tag registry.domain.cn:5000/ubuntu:15 registry.domain.cn:5000/ubuntu:15
[Pipeline] sh
[DockerTestJob] Running shell script
+ docker push registry.domain.cn:5000/ubuntu:15
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Preparing

15: digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4 size: 1357

[Pipeline] End of Pipeline
Finished: SUCCESS

Проверяем сам образ:

[simterm]

$ docker run -ti registry.domain.cn:5000/ubuntu:15 whoami
root

[/simterm]

Готово.

Docker Compose для Jenkins в Docker

Последний шаг – добавить Docker Compose файл для запуска Jenkins.

Билдим Jenkins с нормальным именем:

[simterm]

$ docker build -t registry.domain.cn:5000/tagjenkins .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM jenkins
...
Successfully tagged registry.domain.cn:5000/tagjenkins:latest

[/simterm]

Пушим:

[simterm]

$ docker push registry.domainr.cn:5000/tagjenkins 
The push refers to a repository [registry.domain.cn:5000/tagjenkins]
...

[/simterm]

Создаём Compose:

version: '3'

services:
  jenkins:
    image: "registry.domain.cn:5000/testjenk:1"
    restart: "always"
    ports:
      - "80:8080"
    volumes:
     - /jenkins/:/jenkins
     - /var/run/docker.sock:/var/run/docker.sock
    environment:
     - JENKINS_HOME=/jenkins
  maven:
    image: "maven:3.3.9-jdk-8"
  packer:
    image: "hashicorp/packer:light"
  terraform:
    image: "hashicorp/terraform:light"
  aws-cli:
    image: "fstab/aws-cli"

Запускаем:

[simterm]

$ docker-compose up
Creating network "jenkins_default" with the default driver
Pulling maven (maven:3.3.9-jdk-8)...
3.3.9-jdk-8: Pulling from library/maven
Digest: sha256:18e8bd367c73c93e29d62571ee235e106b18bf6718aeb235c7a07840328bba71
Status: Downloaded newer image for maven:3.3.9-jdk-8
Pulling packer (hashicorp/packer:light)...
light: Pulling from hashicorp/packer
Digest: sha256:311761d935d7170c7b38caf71f84a99735648d7153050e87699b4d3e19d9ad14
Status: Downloaded newer image for hashicorp/packer:light
Pulling aws-cli (fstab/aws-cli:latest)...
latest: Pulling from fstab/aws-cli
Digest: sha256:01a8e8ada9585578d59daa01127a83439a70f9de842542801c18c903c52fbb73
Status: Downloaded newer image for fstab/aws-cli:latest
Pulling terraform (hashicorp/terraform:light)...
light: Pulling from hashicorp/terraform
Digest: sha256:1cf62ff7e0bbb1d8aca82a86c498423b3b057c7f18dcf641b4a1ec3720f93471
Status: Downloaded newer image for hashicorp/terraform:light
Creating jenkins_terraform_1 ... 
Creating jenkins_aws-cli_1 ... 
Creating jenkins_maven_1 ... 
Creating jenkins_jenkins_1 ... 
Creating jenkins_packer_1 ... 
Creating jenkins_terraform_1
Creating jenkins_aws-cli_1
Creating jenkins_maven_1
Creating jenkins_packer_1
Creating jenkins_aws-cli_1 ... done
Attaching to jenkins_terraform_1, jenkins_maven_1, jenkins_packer_1, jenkins_jenkins_1, jenkins_aws-cli_1
...

[/simterm]

Проверяем:

[simterm]

$ curl -s --user user:pass http://build.domain.cn/api/json | jq -r '.jobs[].name'
DockerTestJob

[/simterm]

Готово.