Ansible: миграция RTFM 2.2 – RTFM Jenkins provision

Автор: | 08/10/2017
 

Продолжение сетапа Jenkins для RTFM.

Начало – AWS: CloudFormation для EC2 c Jenkins.

Для того, что бы развернуть Jenkins на EC2 в созданном стеке – потребуются:

  • Docker для запуска самого Jenkins
  • docker-compose файл для запуска Jenkins и подключения разделов

Т.к. CI для развёртывания CI не будет 🙂 – то добавлю скрипт, который будет запускать создание стека а потом выполнять установку и настройку сервисов.

Т.е, идея такова:

  1. наверно – стоит выполнить снапшот EBS перед провиженом SloudFormation стека с EC2 – just in case
  2. скрипт запускает Ansible и выполняет провижен инстанса
    1. устанавливает Docker
    2. копирует на 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.

Тут надо будет:

  1. создать каталог /jenkins
  2. смонтировать /dev/xvdb1 в /jenkins
  3. скопировать файл docker-compose.yml на хост
  4. запустить 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]

Проверяем:

[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

[/simterm]

Ссылки  по теме

Файлы, шаблоны, скрипт – тут>>>.

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