Продолжение сетапа Jenkins для RTFM.
Начало — AWS: CloudFormation для EC2 c Jenkins.
Для того, что бы развернуть Jenkins на EC2 в созданном стеке — потребуются:
- Docker для запуска самого Jenkins
- docker-compose файл для запуска Jenkins и подключения разделов
Т.к. CI для развёртывания CI не будет 🙂 — то добавлю скрипт, который будет запускать создание стека а потом выполнять установку и настройку сервисов.
Т.е, идея такова:
- наверно — стоит выполнить снапшот EBS перед провиженом SloudFormation стека с EC2 — just in case
- скрипт запускает Ansible и выполняет провижен инстанса
- устанавливает Docker
- копирует на CI хост Docker Compose файл, и запускает Jenkins с монтированием раздела с данными Jenkins
Ограничение доступа к CI хосту — на уровне AWS Security Group + авторизация в самом Jenkins.
Получившийся проект — в Github.
Поехали.
Содержание
Создание стека Jenkins
Создание шаблона описано в посте AWS: CloudFormation для EC2 c Jenkins (только шаблон в другом месте и немного изменён, см тут>>>).
Перед написанием ролей, что бы было где тестить роли — создаём стек:
[simterm]
$ aws cloudformation create-stack --stack-name rtfm-jenkins --template-body file://rtfm_jenkins_stack.json --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32
[/simterm]
Стек готов:
[simterm]
$ aws cloudformation describe-stacks --stack-name rtfm-jenkins --query 'Stacks[*].StackStatus'
[
"CREATE_COMPLETE"
]
[/simterm]
Проверяем доступ:
[simterm]
$ ssh [email protected] -i ~/Work/RTFM/Bitbucket/aws-credentials/rtfm-jenkins.pem ... admin@ip-172-31-31-17:~$
[/simterm]
Диск с данными Jenkins — /dev/xvdb:
[simterm]
admin@ip-172-31-31-17:~$ 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 8G 0 disk └─xvdb1 202:17 0 8G 0 part
[/simterm]
Добавляем субдомен ci.rtfm.co.ua (пока — в панели регистратора):
Ansible
ansible.cfg
Начнём с создания локального файла настроек.
Переменные в него будут передаваться из скрипта:
[defaults] ansible_connection=ssh remote_user=admin host_key_checking=False inventory = hosts private_key_file = $JENKINS_RSA_KEY
inventory
Тут же создаём файл hosts, в который вносим FQDN сервера с Jenkins:
[jenkins] ci.rtfm.co.ua
playbook
И плейбук — файл jenkins-provision.yml, в roles пока ничего::
- hosts: jenkins
become:
true
roles:
Содержимое репозитория сейчас:
[simterm]
$ ls -l total 16 -rw-r--r-- 1 setevoy setevoy 131 Oct 8 12:51 ansible.cfg -rw-r--r-- 1 setevoy setevoy 24 Oct 8 12:52 hosts -rw-r--r-- 1 setevoy setevoy 84 Oct 8 12:58 jenkins-provision.yml -rw-r--r-- 1 setevoy setevoy 32 Oct 8 12:49 README.md
[/simterm]
Роли
Docker и Docker Compose
На сервере надо будет установить Docker и Docker Compose для запуска Jenkins.
В jenkins-provision.yml добавляем:
... roles: - angstwad.docker_ubuntu - franklinkim.docker-compose
Создаём requirements.yml, добавляем в него зависимости:
- src: mongrelion.docker - src: franklinkim.docker-compose
Проверяем.
Устанавливаем зависимости локально:
[simterm]
$ ansible-galaxy install --role-file requirements.yml - downloading role 'docker', owned by mongrelion - downloading role from https://github.com/mongrelion/ansible-role-docker/archive/master.tar.gz - extracting mongrelion.docker to /home/setevoy/.ansible/roles/mongrelion.docker - mongrelion.docker (master) was installed successfully - downloading role 'docker-compose', owned by franklinkim - downloading role from https://github.com/weareinteractive/ansible-docker-compose/archive/1.2.2.tar.gz - extracting franklinkim.docker-compose to /home/setevoy/.ansible/roles/franklinkim.docker-compose - franklinkim.docker-compose (1.2.2) was installed successfully
[/simterm]
Проверяем синтаксис плейбука:
[simterm]
$ ansible-playbook --syntax-check jenkins-provision.yml playbook: jenkins-provision.yml
[/simterm]
Запускаем установку:
[simterm]
$ ansible-playbook jenkins-provision.yml --private-key=/home/setevoy/Work/RTFM/Bitbucket/aws-credentials/rtfm-jenkins.pem
[/simterm]
Проверяем Docker на хосте:
[simterm]
$ ansible -i hosts jenkins -a 'docker --version' --private-key=../../Bitbucket/aws-credentials/rtfm-jenkins.pem ci.rtfm.co.ua | SUCCESS | rc=0 >> Docker version 17.09.0-ce, build afdb6d4
[/simterm]
Compose:
[simterm]
$ ansible -i hosts jenkins -a 'docker-compose --version' --private-key=../../Bitbucket/aws-credentials/rtfm-jenkins.pem ci.rtfm.co.ua | SUCCESS | rc=0 >> docker-compose version 1.16.1, build 6d1ac219
[/simterm]
Jenkins
Теперь — создаём роль Jenkins.
Тут надо будет:
- создать каталог
/jenkins - смонтировать
/dev/xvdb1в/jenkins - скопировать файл
docker-compose.ymlна хост - запустить
docker-compose
Создаём каталоги:
[simterm]
$ mkdir -p roles/rtfm-jenkins/{tasks,templates,vars}
[/simterm]
В vars/main.yml добавляем переменные с именем раздела и каталогом монтирования:
ebs_volume: "/dev/xvdb1" jenkins_mount_path: "/jenkins/"
В templates — создаём docker-compose.yml.j2:
version: '2'
services:
jenkins:
user: root
image: jenkins
ports:
- '80:8080'
volumes:
- {{ jenkins_mount_path }}:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JENKINS_HOME=/var/jenkins_home
В tasks/main.yml — создаём задачи — создать каталог, смонтировать раздел, скопировать шаблон и запустить docker-compose:
- name: Create "{{ jenkins_mount_path }}" directory
file:
path: /jenkins
owner: root
group: root
mode: 0755
state: directory
- name: Mount EBS "{{ ebs_volume }}"
mount:
path: "{{ jenkins_mount_path }}"
src: "{{ ebs_volume }}"
state: mounted
fstype: ext4
- template:
src: templates/docker-compose.yml.j2
dest: /home/admin/docker-compose.yml
owner: root
group: root
mode: 0644
- name: Start Jenkins service
docker_service:
project_src: /home/admin
Проверяем синтаксис:
[simterm]
$ ansible-playbook --syntax-check jenkins-provision.yml playbook: jenkins-provision.yml
[/simterm]
И запускаем:
[simterm]
$ ansible-playbook jenkins-provision.yml --private-key=/home/setevoy/Work/RTFM/Bitbucket/aws-credentials/rtfm-jenkins.pem ... PLAY [jenkins] **** TASK [Gathering Facts] **** ok: [ci.rtfm.co.ua] TASK [rtfm-jenkins : Create "/jenkins/" directory] **** ok: [ci.rtfm.co.ua] TASK [rtfm-jenkins : Mount EBS "/dev/xvdb1"] **** ok: [ci.rtfm.co.ua] TASK [rtfm-jenkins : template] **** changed: [ci.rtfm.co.ua] TASK [rtfm-jenkins : Start Jenkins service] **** changed: [ci.rtfm.co.ua] PLAY RECAP **** ci.rtfm.co.ua : ok=5 changed=2 unreachable=0 failed=0
[/simterm]
Проверяем:
ОК — вроде всё работает.
Скрипт запуска
Удаляем стек:
[simterm]
$ aws cloudformation delete-stack --stack-name rtfm-jenkins
[/simterm]
Создаём скрипт:
#!/usr/bin/env bash
RSA_LOCAL_PATH="/home/setevoy/Work/RTFM/Bitbucket/aws-credentials/rtfm-jenkins.pem"
JENKINS_STACK_WORKDIR="/home/setevoy/Work/RTFM/Github/rtfm-blog-cf-templates"
JENKINS_STACK_TEMPLATE="rtfm_jenkins_stack.json"
JENKINS_ANSIBLE_WORKDIR="/home/setevoy/Work/RTFM/Github/rtfm-jenkins-ansible-provision/"
JENKINS_ANSIBLE_PLAYBOOK="jenkins-provision.yml"
JENKINS_EBS_ID="vol-0085149b3a0a45d0c"
HELP="\n\tCreate and provision Jenkins stack script.\n\n\t-b: backup Jenkins EBS to S3\n\t-c: run Stack create (use -s for Stack name!)\n\t-u: run Stack update (use -s for Stack name!)\n\t-a: run Ansible playbook\n\t-i: Allowed IP\n\t-s: Stack name\n\t-h: print this Help\n"
backup_ebs=
create_stack=
update_stack=
run_ansible=
allow_ip=
stack_name=
while getopts "bcuai:s:h" opt; do
case $opt in
b)
backup_ebs=1
echo "Creating stack"
;;
c)
create_stack=1
echo "Creating stack"
;;
u)
update_stack=1
echo "Update stack"
;;
a)
run_ansible=1
echo "Run Ansible"
;;
i)
allow_ip=$OPTARG
echo "Allowed IP $allow_ip"
;;
s)
stack_name=$OPTARG
echo "Stack name $stack_name"
;;
h) echo -e "$HELP"
;;
esac
done
create_ebs_backup () {
now=$(date +"%y-%m-%d-%H-%M")
aws ec2 create-snapshot --volume-id $JENKINS_EBS_ID --description "$now Jenkins EBS backup"
}
create_aws_stack () {
aws cloudformation create-stack --stack-name $stack_name --template-body file://$JENKINS_STACK_WORKDIR/$JENKINS_STACK_TEMPLATE --parameters ParameterKey=HomeAllowLocation,ParameterValue=$allow_ip/32
}
update_aws_stack () {
aws cloudformation update-stack --stack-name $stack_name --template-body file://$JENKINS_STACK_WORKDIR/$JENKINS_STACK_TEMPLATE --parameters ParameterKey=HomeAllowLocation,ParameterValue=$allow_ip/32
}
ansible_run_playbook () {
cd $JENKINS_ANSIBLE_WORKDIR || exit 1
ansible-galaxy install --role-file requirements.yml || exit 1
ansible-playbook --syntax-check $JENKINS_ANSIBLE_PLAYBOOK || exit 1
ansible-playbook $JENKINS_ANSIBLE_PLAYBOOK --private-key=$RSA_LOCAL_PATH
}
if [[ $backup_ebs == 1 ]]; then
echo -e "\nRunning Jenkins EBS backup...\n"
create_ebs_backup && echo -e "\nDone.\n" || { echo -e "ERROR: can't finish backup. Exit.\n"; exit 1; }
fi
if [[ $create_stack == 1 ]] && [[ $stack_name ]] && [[ $allow_ip ]]; then
echo -e "\nCreating Jenkins CloudFormation stack $stack_name with AllowedIP $allow_ip...\n"
create_aws_stack && echo -e "\nDone.\n" || { echo -e "ERROR: can't execute create-stack. Exit.\n"; exit 1; }
fi
if [[ $update_stack == 1 ]] && [[ $stack_name ]] && [[ $allow_ip ]]; then
echo -e "\nUpdating Jenkins CloudFormation stack $stack_name with AllowedIP $allow_ip...\n"
update_aws_stack && echo -e "\nDone.\n" || { echo -e "ERROR: can't execute create-stack. Exit.\n"; exit 1; }
fi
if [[ $run_ansible == 1 ]]; then
echo -e "\nRunning Jenkins Ansible playbook...\n"
ansible_run_playbook && echo -e "\nDone.\n" || { echo -e "ERROR: can't finish backup. Exit.\n"; exit 1; }
fi
Помощь:
[simterm]
$ ./rtfm-jenkins-provision.sh -h
Create and provision Jenkins stack script.
-b: backup Jenkins EBS to S3
-c: run Stack create (use -s for Stack name!)
-u: run Stack update (use -s for Stack name!)
-a: run Ansible playbook
-i: Allowed IP
-s: Stack name
-h: print this Help
[/simterm]
Теперь — создаём стек:
[simterm]
$ ./rtfm-jenkins-provision.sh -c -s rtfm-jenkins -i 188.***.***.114
Creating stack
Stack name rtfm-jenkins
Allowed IP 188.***.***.114
Creating Jenkins CloudFormation stack rtfm-jenkins with AllowedIP 188.***.***.114...
{
"StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/rtfm-jenkins/42e08320-ac3a-11e7-a2ea-50a6863424c5"
}
Done.
[/simterm]
Ждём завершения, обновляем DNS на новый IP, пока ждём несколько минут обновления DNS — можно сделать бекап раздела:
[simterm]
$ ./rtfm-jenkins-provision.sh -b
Backing up EBS
Running Jenkins EBS backup...
{
"Description": "17-10-08-18-08 Jenkins EBS backup",
"Encrypted": false,
"OwnerId": "264418146286",
"Progress": "",
"SnapshotId": "snap-0d57ac7073360f9db",
"StartTime": "2017-10-08T15:08:17.000Z",
"State": "pending",
"VolumeId": "vol-0085149b3a0a45d0c",
"VolumeSize": 8
}
Done.
[/simterm]
Запускаем Ansible:
[simterm]
$ ./rtfm-jenkins-provision.sh -a Run Ansible Running Jenkins Ansible playbook... [WARNING]: - mongrelion.docker (master) is already installed - use --force to change version to unspecified [WARNING]: - franklinkim.docker-compose (1.2.2) is already installed - use --force to change version to unspecified playbook: jenkins-provision.yml PLAY [jenkins] **** TASK [Gathering Facts] **** ok: [ci.rtfm.co.ua] TASK [mongrelion.docker : include] **** included: /home/setevoy/.ansible/roles/mongrelion.docker/tasks/os/Debian.yml for ci.rtfm.co.ua ... TASK [rtfm-jenkins : Start Jenkins service] **** changed: [ci.rtfm.co.ua] PLAY RECAP **** ci.rtfm.co.ua : ok=22 changed=10 unreachable=0 failed=0 Done.
Проверяем:
[simterm]
admin@ip-172-31-21-55:~$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f9b86c46ac4 jenkins "/bin/tini -- /usr..." About a minute ago Up About a minute 50000/tcp, 0.0.0.0:80->8080/tcp admin_jenkins_1
Ссылки по теме
Файлы, шаблоны, скрипт — тут>>>.
BASH: функция getopts – используем опции в скриптах
AWS: CloudFormation для EC2 c Jenkins
Ansible: ansible-galaxy – репозиторий ролей и Jenkins VM provision
Ansible: роли для Docker Compose, Prometheus и node_exporter



