В одном из предыдущих постов – Jenkins: миграция RTFM 2.4 – Jenkins Pipeline для CloudFormation RTFM стека – была добавлена задача в Jenkins для создания и апдейта AWS CloudFormation стека.
Следующая задача – запускать Ansbile из Jenkins для настройки серверов в стеке.
Далее создадим одну Ansbile роль с установкой NGINX, потом добавим задачу в Jenkins.
PEM-ключи для авторизации на EC2 и шаблоны для конфигурации виртуалхостов хранятся в приватном репозитории Bitbucket.
Содержание
Подготовка
Запускаем Jenkins – Ansible: миграция RTFM 2.2 – RTFM Jenkins provision.
Создаём стек rtfm-dev – Jenkins: миграция RTFM 2.4 – Jenkins Pipeline для CloudFormation RTFM стека.
Находим Public IP в outputs
, создаём субдомен dev.rtfm.co.ua:
Ansible
ansible.cfg
Начнём с создания локального файла настроек:
[defaults] ansible_connection=ssh remote_user=admin host_key_checking=False inventory = hosts
inventory
Тут же создаём файл hosts
, в который вносим FQDN Bastion хоста:
[rtfm-dev] dev.rtfm.co.ua
playbook
И файл плейбука rtfm-blog-ansible-provision.yml
, пока с одной ролью – nginx
:
- hosts: all become: true roles: - nginx
.gitignore
Т.к. в роли будут использоваться шаблоны виртуалхостов, которые будут хранится в приватном репозитории Bitbuket и загружаться в каталог roles/nginx/templates/virtualhosts
во время провижена самим Jenkins – сразу добавляем .gitignore
:
[simterm]
$ cat .gitignore *.retry *.swp
[/simterm]
*.retry
– не коммитить retry-файлы Ansible (либо вообще отключить их создание через ansible.cfg
) и *.swp
– игнорировать временные файлы vim
.
И ещё один в каталогеroles/nginx/templates/virtualhosts
– игнорировать всё, кроме .gitignore
и nginx.conf
(он стандартный):
* !.gitignore !nginx.conf
Роль nginx
Пример есть в посте Ansible: пример установки NGINX, тут особо отличий нет.
Создаём каталоги tasks
и templates
– пока этого хватит:
[simterm]
$ mkdir -p roles/nginx/{tasks,templates}
[/simterm]
В roles/nginx/templates/
создаём шаблон для файла настроек NGINX – тоже дефолтный, только с log_proxy
, переменные и параметры пропишем позже:
user www-data; worker_processes auto; pid /var/run/nginx.pid; events { worker_connections 1024; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; log_format proxy '[$time_local] $remote_addr - $server_name to: ' '$upstream_addr: $request upstream_response_time ' '$upstream_response_time msec $msec request_time $request_time'; gzip on; gzip_disable "msie6"; include /etc/nginx/conf.d/*.conf; }
В каталоге roles/nginx/templates/virtualhosts/
создаём файл шаблона для виртуалхоста (ещё раз – эти шаблоны будут в Bitbucket, сейчас просто для проверки создаём тут) – roles/nginx/templates/virtualhosts/dev.rtfm.co.ua.conf
:
server { server_name {{ inventory_hostname }}; listen 80; access_log /var/log/nginx/{{ inventory_hostname }}-access.log proxy; error_log /var/log/nginx/{{ inventory_hostname }}-error.log notice; root /var/www/html; index index.html index.htm index.nginx-debian.html; location / { try_files $uri $uri/ =404; } }
В {{ inventory_hostname }}
– будет подставлено имя из файла hosts
, в данном случае – dev.rtfm.co.ua.
В tasks
создаём файл main.yml
с четырьмя задачами – установка NGINX, копирование файла настроек, копирование шаблона виртуалхоста, reload NGINX:
- name: Install Nginx package: name: nginx state: latest - name: Replace NGINX config template: src=templates/nginx.conf dest=/etc/nginx/nginx.conf - name: Add NGINX {{ inventory_hostname }} virtualhost config template: src=templates/virtualhosts/{{ inventory_hostname }}.conf dest=/etc/nginx/conf.d/{{ inventory_hostname }}.conf - name: Service NGINX reload service: name=nginx state=reloaded
Ansible syntax-check и запуск
Проверяем получившийся плейбук:
[simterm]
$ ansible-playbook --syntax-check --limit=rtfm-dev rtfm-blog-ansible-provision.yml playbook: rtfm-blog-ansible-provision.yml
[/simterm]
Всё ОК – можно запускать на выполнение:
[simterm]
$ ansible-playbook --limit=rtfm-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-provision.yml PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.rtfm.co.ua] TASK [nginx : Install Nginx] **** changed: [dev.rtfm.co.ua] TASK [nginx : Replace NGINX config] **** changed: [dev.rtfm.co.ua] TASK [nginx : Add NGINX dev.rtfm.co.ua virtualhost config] **** changed: [dev.rtfm.co.ua] TASK [nginx : Service NGINX reload] **** changed: [dev.rtfm.co.ua] PLAY RECAP **** dev.rtfm.co.ua : ok=5 changed=4 unreachable=0 failed=0
[/simterm]
Проверяем:
[simterm]
$ curl -I dev.rtfm.co.ua HTTP/1.1 200 OK Server: nginx/1.10.3 Date: Wed, 18 Oct 2017 14:29:07 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Wed, 18 Oct 2017 14:28:25 GMT Connection: keep-alive ETag: "59e76509-264" Accept-Ranges: bytes
[/simterm]
Всё работает.
Сохраняем изменения в репозиторий:
[simterm]
$ git add -A && git commit -m "RTFM Dev Nginx Ansible provision" [master 928f230] RTFM Dev Nginx Ansible provision 6 files changed, 67 insertions(+) create mode 100644 .gitignore create mode 100644 ansible.cfg create mode 100644 hosts create mode 100644 roles/nginx/tasks/main.yml create mode 100644 roles/nginx/templates/nginx.conf create mode 100644 rtfm-blog-ansible-provision.yml $ git push Counting objects: 12, done. Delta compression using up to 4 threads. Compressing objects: 100% (7/7), done. Writing objects: 100% (12/12), 1.46 KiB | 749.00 KiB/s, done. Total 12 (delta 0), reused 0 (delta 0) To https://github.com/setevoy2/rtfm-blog-ansible-provision.git ca14741..928f230 master -> master
[/simterm]
Bitbucket
В Bitbucket – создаём приватный репозиторий rtfm-blog-ansible-templates:
[simterm]
$ git clone https://[email protected]/username/rtfm-blog-ansible-templates.git Cloning into 'rtfm-blog-ansible-templates'... Password for 'https://[email protected]': warning: You appear to have cloned an empty repository.
[/simterm]
Добавляем шаблон roles/nginx/templates/virtualhosts/dev.rtfm.co.ua.conf
:
[simterm]
$ cp ../Github/rtfm-blog-ansible-provision/roles/nginx/templates/virtualhosts/dev.rtfm.co.ua.conf rtfm-blog-ansible-templates/ $ cd rtfm-blog-ansible-templates/ && git add -A && git commit -m "NGINX Dev virtualhost config template" && git push [master (root-commit) aaf9e4d] NGINX Dev virtualhost config template 1 file changed, 17 insertions(+) create mode 100644 dev.rtfm.co.ua.conf Password for 'https://[email protected]': Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 431 bytes | 431.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://bitbucket.org/username/rtfm-blog-ansible-templates.git * [new branch] master -> master
[/simterm]
Jenkins
Credentials
В Jenkins добавляем данные доступа к Bitbucket.
Создаём новый домен в Credentials:
Добавляем пользоваля и пароль:
Pipeline
Теперь можно создать новую задачу.
В принципе – ничего особо отличного от поста Jenkins: миграция RTFM 2.4 – Jenkins Pipeline для CloudFormation RTFM стека.
Скрипты будут в репозитории rtfm-jenkins-scripts.
Создаём новую View – rtfm-ansible-provision:
Добавляем новую задачу, можно просто скопировать из задачи провижена стека CloudFormation:
ANSIBLE_HOST_LIMIT
– для передачи вansbile-playbook --limit
, тут rtfm-devANSIBLE_PLAYBOOK_FILE
– файлrtfm-blog-ansible-provision.yml
ANSIBLE_EC2_PEM_FILE
– pem-ключ для доступа к EC2 интансуANSIBLE_GITHUB_REPO_URL
– репозиторий с плейбуком и ролями, созданный выше, https://github.com/setevoy2/rtfm-blog-ansible-provision.gitANSIBLE_GITHUB_BRANCH
– его бранч, masterANSIBLE_BB_TEMPLATES_REPO_URL
– репозиторий Bitbucket с шаблонами виртуалхостов, https://[email protected]/username/rtfm-blog-ansible-templates.gitANSIBLE_BB_TEMPLATES_BRANCH
– его бранч, masterANSIBLE_BB_CREDENTIALS_REPO_URL
– Bitbucket репозиторий с PEM-ключами, бранч тут всегда master, https://[email protected]/username/aws-credentials.gitCI_BRANCH
– бранч скриптов Jenkins, masterCI_SCRIPTS_REPO_URL
– репозиторий скриптов Jenkins, https://github.com/setevoy2/rtfm-jenkins-scripts.git
И параметры Pipeline репозитория – тут меняется только имя скрипта – rtfm-blog-ansible-provision.groovy
:
Jenkinsfile-s
Теперь всё готово – можно добавлять скрипты.
Как и в случае с провиженом CloudFormation – все функции держим в одном скрипте – provision.groovy
, их вызовы – в другом, в данном случае rtfm-blog-ansible-provision.groovy
.
Сначала добавляем функцию для проверки синтаксиса.
Обновляем скрипт provision.groovy
, добавляем функцию ansiblePlaybookValidate()
:
... def ansiblePlaybookValidate(ansibleHostLimit='1', ansiblePlaybookFile='2') { docker.image('williamyeh/ansible:master-ubuntu16.04').inside('-v /var/run/docker.sock:/var/run/docker.sock') { git branch: "${ANSIBLE_GITHUB_BRANCH}", url: "${ANSIBLE_GITHUB_REPO_URL}" stage('Ansible playbook validate') { sh "ansible-playbook --syntax-check --limit=${ansibleHostLimit} ${ansiblePlaybookFile}" } } } ...
И создаём скрипт rtfm-blog-ansible-provision.groovy
:
#!/usr/bin/env groovy node { /* Variables inherited from Jenkins job's settings // String parameters CI_BRANCH = "${CI_BRANCH}" CI_SCRIPTS_REPO_URL = "${CI_SCRIPTS_REPO_URL}" */ dir('ciscripts') { git branch: "${CI_BRANCH}", url: "${CI_SCRIPTS_REPO_URL}" } def provision = load 'ciscripts/provision.groovy' // ansibleHostLimit='1', ansiblePlaybookFile='2' provision.ansiblePlaybookValidate("${ANSIBLE_HOST_LIMIT}", "${ANSIBLE_PLAYBOOK_FILE}") }
Сохраняем в репозиторий:
[simterm]
$ git add provision.groovy && git commit -m "ansiblePlaybookValidate() func added" [master a800c77] ansiblePlaybookValidate() func added 1 file changed, 13 insertions(+) $ git add rtfm-blog-ansible-provision.groovy && git commit -m "ansiblePlaybookValidate() run" && git push [master 63fbf10] ansiblePlaybookValidate() run 1 file changed, 22 insertions(+) create mode 100644 rtfm-blog-ansible-provision.groovy Counting objects: 6, done. Delta compression using up to 4 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 1.16 KiB | 1.16 MiB/s, done. Total 6 (delta 2), reused 0 (delta 0) remote: Resolving deltas: 100% (2/2), completed with 1 local object. To https://github.com/setevoy2/rtfm-jenkins-scripts.git da18411..63fbf10 master -> master
[/simterm]
Запускаем билд:
Теперь можно добавить функцию для запуска ansible-playbook
в скрипт provision.groovy
– ansiblePlaybookApply()
:
... def ansiblePlaybookApply(ansibleHostLimit='1', ansiblePlaybookFile='2', ansiblePemFile='3') { docker.image('williamyeh/ansible:master-ubuntu16.04').inside('-v /var/run/docker.sock:/var/run/docker.sock') { git branch: "${ANSIBLE_GITHUB_BRANCH}", url: "${ANSIBLE_GITHUB_REPO_URL}" dir('credentials') { git branch: "master", credentialsId: 'setevoy_bitbucket_aws', url: "${ANSIBLE_BB_CREDENTIALS_REPO_URL}" } dir('roles/nginx/templates/virtualhosts') { git branch: "${ANSIBLE_BB_TEMPLATES_BRANCH}", credentialsId: 'setevoy_bitbucket_aws', url: "${ANSIBLE_BB_TEMPLATES_REPO_URL}" } stage('Ansible playbook apply') { sh "chmod 400 credentials/${ANSIBLE_EC2_PEM_FILE}" sh "ansible-playbook --limit=${ansibleHostLimit} --private-key=credentials/${ansiblePemFile} ${ansiblePlaybookFile}" } } } ...
Сначала git
выполняет checkout ${ANSIBLE_GITHUB_REPO_URL}
.
Потом – создаёт каталог credentials, и в него загружает репозиторий ${ANSIBLE_BB_CREDENTIALS_REPO_URL}
.
И третьим – создаёт каталог roles/nginx/templates/virtualhosts, в который загружает содержимое ${ANSIBLE_BB_TEMPLATES_REPO_URL}
.
Один нюанс тут: в шаблоне CloudFormation создаётся SecurityGroup с доступом к порту 22 только с моего домашнего IP – надо руками добавить ещё одно правило для доступа с IP хоста с Jenkins, т.к. он постоянно меняется. Позже можно будет обновить шаблон и использовать Fn::ImportValue
, что бы полчить IP Jenkins из его стека.
Добавляем вызов функции в скрипт rtfm-blog-ansible-provision.groovy
и передаём ей параметры (для Production, соответственно – будут другие параметры в Jenkins job-е, которые и будут подставляться в аргументы функции):
... // ansibleHostLimit='1', ansiblePlaybookFile='2', ansiblePemFile='3' provision.ansiblePlaybookApply("${ANSIBLE_HOST_LIMIT}", "${ANSIBLE_PLAYBOOK_FILE}", "${ANSIBLE_EC2_PEM_FILE}") ...
Сохраняем, запускаем:
[simterm]
$ git add provision.groovy rtfm-blog-ansible-provision.groovy && git commit -m "ansiblePlaybookApply() added" && git push
[/simterm]
Проверяем
Всё удаляем – и создаём заново.
Удаляем стек:
[simterm]
$ aws cloudformation delete-stack --stack-name rtfm-dev
[/simterm]
Запускаем rtfm-blog-dev-cf-provision в Jenkins:
Обновляем IN A
для dev.rtfm.co.ua, ждём обновления DNS:
[simterm]
$ dig dev.rtfm.co.ua +short
34.250.106.205
[/simterm]
Обновляем (пока вручную) Security Group, даём доступ с IP Jenkins.
Запускаем rtfm-blog-dev-ansible-provision:
Проверяем:
[simterm]
$ curl -I dev.rtfm.co.ua HTTP/1.1 200 OK Server: nginx/1.10.3 Date: Wed, 18 Oct 2017 16:28:40 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Wed, 18 Oct 2017 16:26:59 GMT Connection: keep-alive ETag: "59e780d3-264" Accept-Ranges: bytes
[/simterm]
Готово.