В одном из предыдущих постов — 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.ymlANSIBLE_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]
Готово.










