Продолжение сетапа 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
Jenkins: Pipeline, Groovy, Ansible и VM provisioning