Продолжение сетапа инфрастуктуры для блога.
Решил добавить в заголовки “версию” каждого поста, в данном случае – миграция RTFM 2.4. Тут мажорная версия “2” – потому что год тому уже что-то подобное начинал делать (AWS: миграция RTFM, часть #1: ручное создание инфраструктуры – VPC, подсети, IGW, NAT GW, маршруты и EC2), а минорная “4” – четвёртый пост в серии.
Предыдущие посты этой серии:
- AWS: CloudFormation для EC2 c Jenkins: создание шаблона для Jenkins, который будет обновлять стек RTFM
- Ansible: RTFM Jenkins provision: установка и настройка Jenkins для RTFM
- AWS: инфраструктура для RTFM и создание CloudFormation шаблона – VPC, subnets, EC2: создание шаблона AWS CloudFormation для стека блога
Итого, сейчас имеются шаблоны для развёртывания EC2 с Jenkins, Ansible плейбук для установки и запуска Jenkins и шаблон для развёртывания CloudFormation стека блога.
Ссылки на репозитории:
- rtfm-jenkins-ansible-provision – Ansible роли для запуска Jenkins
- rtfm-blog-cf-templates – шаблоны AWS CloudFormation
- rtfm-jenkins-scripts – скрипты Jenkins, которые получились в результате написания этого поста
Теперь можно приступать к созданию задач в Jenkins, которые будут заниматься созданием и апдейтом стека и настройкой серверов в нём.
В этом посте только создание и апдейт стека – для Ansible – ещё предстоит написать роли, а потом добавить ещё одну задачу в Jenkins.
Содержание
Подготовка
Что бы упростить проверку статуса – в .bashrc
добавляем алиас:
alias checkjenkinsstatus="aws cloudformation describe-stacks --stack-name rtfm-jenkins --query 'Stacks[*].StackStatus' --output text"
И ещё один – что бы получать PublicIP:
alias getrtfmjenkinsip="aws ec2 describe-instances --filters Name=tag-value,Values=rtfm-jenkins-ec2 --query 'Reservations[*].Instances[*].PublicIpAddress' --output text"
Создаём стек Jenkins скриптом из поста Ansible: RTFM Jenkins provision:
[simterm]
$ cd /home/setevoy/Work/RTFM/Github/rtfm-jenkins-ansible-provision/ && git pull $ ./rtfm-jenkins-provision.sh -b $ ./rtfm-jenkins-provision.sh -c -s rtfm-jenkins -i 188.***.***.114
[/simterm]
Проверяем статус:
[simterm]
$ checkjenkinsstatus CREATE_COMPLETE
[/simterm]
Получаем IP:
[simterm]
$ getrtfmjenkinsip 34.240.186.116
[/simterm]
Обновляем имя ci.rtfm.co.ua у регистратора, делаем чай, пока обновляются DNS.
Проверяем:
[simterm]
$ dig +short ci.rtfm.co.ua 34.240.186.116
[/simterm]
Запускаем Ansible:
[simterm]
$ ./rtfm-jenkins-provision.sh -a
[/simterm]
Jenkins готов к работе.
Jenkins
View
Создаём новую View – rtfm-cf-provision:
Jenkinsfile
Теперь – можно добавить Groovy-скрипт для Jenkins Pipeline, который будет запускать Docker контейнер с AWS CLI и выполнять aws cloudformation stack-update
.
Пост по теме – Azure: provisioning с Resource Manager, Jenkins и Groovy.
Создание шаблона для RTFM, который используется дальше – rtfm-blog-cf-template.json – описано в посте AWS: миграция RTFM 2.3 – инфраструктура для RTFM и создание CloudFormation шаблона – VPC, subnets, EC2.
Клонируем пока пустой репозиторий:
[simterm]
$ cd ~/Work/RTFM/Github && git clone [email protected]:setevoy2/rtfm-jenkins-scripts.git $ cd rtfm-jenkins-scripts/
[/simterm]
Скриптов будет два – один с функциями, второй – с переменными и вызовом этих функций, он и будет вызываться в Jenkins.
Первый скрипт – provision.groovy
, пока одна функция – cfTemplateValidate()
:
#!/usr/bin/env groovy def cfTemplateValidate(cfTemplatesRepoUrl='1', cfBranch='2', cfTemplateFile='3') { docker.image('mesosphere/aws-cli').inside('-v /var/run/docker.sock:/var/run/docker.sock') { git branch: "${cfBranch}", url: "${cfTemplatesRepoUrl}" stage('CF Temlate validate') { // sh -e to prevent printing variables in a build log // read https://stackoverflow.com/a/39908900/2720802 sh "#!/bin/sh -e\n" + "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} && export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" sh "aws cloudformation --region eu-west-1 validate-template --template-body file://${cfTemplateFile}" } } } return this
Второй скрипт, rtfm-cf-stack-provision.groovy
– переменные, комментарии и вызов функции cfTemplateValidate()
с передачей ей параметров:
#!/usr/bin/env groovy node { /* Variables inherited from Jenkins job's settings // Password Parameters AWS_ACCESS_KEY_ID = "${AWS_ACCESS_KEY_ID}" AWS_SECRET_ACCESS_KEY = "${AWS_SECRET_ACCESS_KEY}" // String parameters // Environment type - rtfm-dev or rtfm-production. // To be used during stack-create. ENV = "${ENV}" CI_BRANCH = "${CI_BRANCH}" CI_SCRIPTS_REPO_URL = "${CI_SCRIPTS_REPO_URL}" CF_BRANCH = "${CF_BRANCH}" CF_TEMPLATES_REPO_URL = "${CF_TEMPLATES_REPO_URL}" CF_STACK_TEMPLATE_FILE = "${CF_STACK_TEMPLATE_FILE}" */ dir('ciscripts') { git branch: "${CI_BRANCH}", url: "${CI_SCRIPTS_REPO_URL}" } def provision = load 'ciscripts/provision.groovy' // cfTemplatesRepoUrl='1', cfBranch='2', cfTemplateFile='3' provision.cfTemplateValidate("${CF_TEMPLATES_REPO_URL}", "${CF_BRANCH}", "${CF_STACK_TEMPLATE_FILE}") }
Ссылка на репозиторий – в конце поста.
Jenkins job
Создаём первую задачу – rtfm-blog-cf-dev-provision, тип Pipeline:
AWS_ACCESS_KEY_ID
– надо будет создать отдельного пользователя для Jenkins.
Настраиваем репозиторий:
Так как тут используется только репозиторий Github – то Credentials пока не нужны. А вот для Ansible задач, куда надо будет клонировать ключи доступ из приватного репозитория в Bitbucket – добавлю.
И запускаем билд:
Пришлось всё-таки поменять t2.nano
на t2.micro
– Jenkins зависал, когда стартовал контейнеры для билдов.
Можно добавить вторую функцию – create-stack
.
Но тут встал вопрос – что вызывать: create-stack
или update-stack
?
Решение нашлось такое: добавляем функцию cfCheckStackPresent()
, которая выполняет
describe-stacks
ИМЯ_СТЕКА. Если она вернёт ОК – значит стек есть, выполняем update. В противном случае – стека нет, значит – создаём его. --stack-name
Обновляем provision.groovy
, добавляем cfCheckStackPresent()
:
... def cfCheckStackPresent(cfStackName='1') { docker.image('mesosphere/aws-cli').inside('-v /var/run/docker.sock:/var/run/docker.sock') { // sh -e to prevent printing variables in a build log // read https://stackoverflow.com/a/39908900/2720802 sh "#!/bin/sh -e\n" + "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} && export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" sh "aws cloudformation --region eu-west-1 describe-stacks --stack-name ${cfStackName} &> /dev/null" } } ...
И функция cfStackCreateOrUpdate()
, которой первым аргументом передаётся значение create или update, которое далее подставляется в aws cloudformation --region eu-west-1 ${action}-stack
:
... def cfStackCreateOrUpdate(action='1', cfTemplatesRepoUrl='2', cfBranch='3', cfTemplateFile='4', cfStackName='5', env='6', cfKeyName='7', allowLocation='8') { docker.image('mesosphere/aws-cli').inside('-v /var/run/docker.sock:/var/run/docker.sock') { git branch: "${cfBranch}", url: "${cfTemplatesRepoUrl}" stage("CF Stack ${action}") { sh "echo Stack ${action}" // sh -e to prevent printing variables in a build log // read https://stackoverflow.com/a/39908900/2720802 sh "#!/bin/sh -e\n" + "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} && export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" sh "aws cloudformation --region eu-west-1 ${action}-stack --stack-name ${cfStackName} --template-body file://${cfTemplateFile} --parameters ParameterKey=ENV,ParameterValue=${env} ParameterKey=KeyName,ParameterValue=${cfKeyName} ParameterKey=HomeAllowLocation,ParameterValue=${allowLocation}/32" } } } ...
В файле rtfm-cf-stack-provision.groovy
добавляем try-catch и в зависимости от выполнения блока – задаём аргумент action
:
... // action='1', cfTemplatesRepoUrl='2', cfBranch='3', cfTemplateFile='4', cfStackName='5', env='6', cfKeyName='7', allowLocation='8' try { // if cfCheckStackPresent == True then "update" cfCheckStackPresent("${CF_STACK_NAME}") cfStackCreateOrUpdate('update', "${CF_TEMPLATES_REPO_URL}", "${CF_BRANCH}", "${CF_STACK_TEMPLATE_FILE}", "${CF_STACK_NAME}", "${ENV}", "${CF_EC2_KEY_NAME}", "${HOME_ALLOW_LOCATION}") } catch(Exception) { // if cfCheckStackPresent == False then "create" cfStackCreateOrUpdate('create', "${CF_TEMPLATES_REPO_URL}", "${CF_BRANCH}", "${CF_STACK_TEMPLATE_FILE}", "${CF_STACK_NAME}", "${ENV}", "${CF_EC2_KEY_NAME}", "${HOME_ALLOW_LOCATION}") } ...
UPD 27.0.12018 – правда, тут есть одна небольшая проблема, или, скорее, недоработка такого решения – если в шаблоне никаких изменений не было – то билд “падает”, т.к. Jenkins пытается выполнить и update
, и create
.
В параметры Jenkins добавляем ещё 4 переменных:
CF_STACK_NAME = "${CF_STACK_NAME}"
ENV = "${ENV}"
CF_EC2_KEY_NAME = "${CF_EC2_KEY_NAME}"
HOME_ALLOW_LOCATION = "${HOME_ALLOW_LOCATION}"
Сохраняем, запускаем:
AWS Console:
[simterm]
$ ssh [email protected] -i ~/Work/RTFM/Bitbucket/aws-credentials/rtfm-dev.pem The authenticity of host '52.31.49.190 (52.31.49.190)' can't be established. ECDSA key fingerprint is SHA256:Sp0Ovmma/W+3XaJsVTOwfBUmS0ih1T4azBw9EO8iABQ. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '52.31.49.190' (ECDSA) to the list of known hosts. Linux ip-10-0-1-58 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u3 (2017-08-06) x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. admin@ip-10-0-1-58:~$
[/simterm]
Скрипты доступы в репозитории rtfm-jenkins-scripts.
Готово.
P.S. А это – количество билдов, которые понадобились что бы закончить настройку pipeline и скриптов: