Продолжение сетапа инфрастуктуры для блога.
Решил добавить в заголовки «версию» каждого поста, в данном случае — миграция 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 и скриптов:











