Продолжение развёртывания CI инфрастуктуры в Китае. Начало тут>>>.
В предыдущей части – запустили Docker registry, теперь – нужен Jenkins, который будет собирать образы сервисов и пушить в это хранилище.
Для запуска Jenkins потребуется:
- создать EBS для Jenkins workspaces и подключить к EC2
- создать EBS для Docker образов
- запустить и проверить Jenkins
- проверить Docker билды в Jenkins
- подготовить 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.048kBStep 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]
Готово.