Задача — развернуть мониторинг Prometehus + Grafana в AWS (в противоположность Azure на предыдущем проекте…).
Весь стек будет состоять из одного EC2, на котором будет NGINX + Prometheus + Grafana.
Из экспортёров на хосте мониторинга будут node_exporter
и blackbox_exporter
, скорее всего ещё mysql_exporter
— собирать метрики с MariaDB бекенда нашего приложения, и какие-то ещё. Но это ещё не точно.
Вообще сбор метрик с приложения и екпортёры к нему будем думать позже.
Планируемый сетап:
- CloudFormation шаблон для создания окружений
- Ansible для настройки хостов и создания сервисов
- Docker Compose — для управления сервисами мониторинга
- Jenkins — для провижена окружений
Планируемые шаги:
- т.к. поначалу Jenkins jobs не будет — то написать скрипт для создания/апдейта CloudFormation стеков
- к нему добавить тестовый CF-шаблон
- дальше можно начинать создавать CloudFormaition стек для мониторинга, который будет включать в себя:
- VPC
- публичная подсеть
- отдельно ещё ACL
- EС2: собственно, хост мониторинга, на котором всё будет работать
- отдельный EBS для данных Promethus и базы Grafana
- security группа для ограничения доступа к 22, 80, 443
- VPC
- дальше — скрипт для Ansible
- тестовые плейбук и роль
- Jenkins job-ы для запуска CloudFormation и Ansible
Собственно — этим пока и займёмся, а потом перейдём к Ansible, а ещё поже — непосредственно к настройке сбора и отображения метрик.
В этом посте будут только создание пользователя, скрипта и CloudFormation шаблона.
Содержание
Скрипт создания AWS CloudFormation стека
Собственно — тут ничего нового, уже делался аналогичный, см. пост BASH: скрипт создания AWS CloudFormation стека, только тут я ещё добавил getops()
, см. BASH: функция getopts – используем опции в скриптах.
Для написания скрипта потребуются:
- IAM политика с CloudFormation доступом
- IAM пользователь, от которого будет выполняться создание стека
- минимальный CF шаблон
IAM политика
AWS IAM предоставляет только одну готовую политику —
Переходим в IAM > Policies > Create Policy, в JSON-редакторе добавляем политику:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "btrmcf0", "Effect": "Allow", "Action": "cloudformation:*", "Resource": "*" } ] }
Сохраняем её с именем, например, AWSCloudFormationFullAccess.
IAM пользователь
В IAM переходим в Users и создаём пользователя с Programmatic access:
В Permission выбираем Attach existing policies directly, и находим созданную ранее политику:
Сохраняем, и получаем Access и Secret ключи:
Повторяем для уже готовой политики AmazonEC2FullAccess, т.к. нужен будет доступ к EC2 и ключам:
Именованный профиль AWS
Усатанавливаем AWS CLI (рабочая машинка совсем новая):
Далее настраиваем именованный профиль:
Пробуем проверить стеки:
ОК.
Создание тестового стека
Используем шаблон с одним EC2 отсюда
Создаём стек, используя профайл btrm-mon:
Проверяем:
ОК, удаляем его, и переходим к скрипту:
Скрипт
Сам скрипт доступен в
Принимает три обязательных опции с аргументами — имя профайла AWS CLI, имя стека и путь к файлу шаблона:
При запуске — скрипт выполняет валидацию шаблона (cf_template_validate()
), затем проверяет наличие стека, вызывая cf_stack_check_create_or_update()
, которая возвращает список стеков в аккаунте, и сравнивания их с переданным именем стека в параметре -s
:
... # cf_stack_check_create_or_update() will return all stacks in a AWS account as TEXT # test[] below will check with regex =~ if the $STACK_NAME present in the output from the cf_stack_check_create_or_update() echo -e "\nChecking if stack $STACK_NAME already present..." if [[ $(cf_stack_check_create_or_update $PROFILE_NAME) =~ $STACK_NAME ]]; then echo -e "\nStack $STACK_NAME found, preparing Stack Update.\n" CREATE_OR_UPDATE="update" else echo -e "\nStack $STACK_NAME not found, running Stack Create.\n" CREATE_OR_UPDATE="create" fi ...
Далее переменная CREATE_OR_UPDATE
передаётся аргументом функции cf_stack_exec_create_or_update()
, где используется при вызове aws cloudformation --profile $profile $create_or_update-stack
:
... cf_stack_exec_create_or_update () { local profile=$1 local stack_name=$2 local template=$3 local create_or_update=$4 aws cloudformation --profile $profile $create_or_update-stack --stack-name $stack_name --template-body file://$template } ...
Пример выполнения скрипта:
CloudFormation шаблон
Теперь можно создавать сам шаблон.
Тут потребуются такие ресурсы:
- EIP: нужен статический IP
- VPC
- subnet
- ACL
- EC2
- EBS — для данных Prometehus/Grafana
Сам шаблон доступен в репозитории тут —
Кратко его основные ресурсы.
VPC
Все создаваемые ресурсы будут включены в одну
... "VPC" : { "Type" : "AWS::EC2::VPC", "Properties" : { "CidrBlock" : {"Ref" : "VPCCIDRBlock"}, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "vpc"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
В Tags
создаём тег Name
со значением из ИмениСтека + vpc, т.е. в результате получается имя VPC вида btrm-mon-dev-vpc:
Этот же подход используется дальше для всех ресурсов.
{"Key" : "Env", "Value" : {"Ref" : "ENV"} }
используется для создания
PublicSubnet
VPC состоит из одной
... "PublicSubnet" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : {"Ref" : "PublicSubnetCIDR"}, "AvailabilityZone" : {"Ref" : "AvalabilityZone"}, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "public-net"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
В параметрах шаблона задаётся единая
... "AvailabilityZone" : {"Ref" : "AvailabilityZone"}, ...
И сама зона задаётся как:
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "AWS CloudFormation stack with VPC, ACL, EC2, EBS", "Parameters" : { ... "AvailabilityZone": { "Description": "Availability Zone for all resources", "Type": "String", "Default": "eu-west-1a" }, ...
InternetGateway
Для доступа в инетрнет из публичной сети — создаётся ресурс
... "InternetGateway" : { "Type" : "AWS::EC2::InternetGateway", "Properties" : { "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "igw"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
VPC ACL
В дополнение к
... "NetACL": { "Type" : "AWS::EC2::NetworkAcl", "Properties" : { "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "pub-acl"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ], "VpcId" : { "Ref" : "VPC"} } }, ...
У которого в дополнение к дефолным «Deny all» пока всего два правила — «Allow all«:
... "NetACLInAllAllow": { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "CidrBlock" : "0.0.0.0/0", "Egress" : false, "NetworkAclId" : { "Ref" : "NetACL"}, "Protocol" : -1, "RuleAction" : "Allow", "RuleNumber" : 100 } }, "NetACLOutAllAllow": { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "CidrBlock" : "0.0.0.0/0", "Egress" : true, "NetworkAclId" : { "Ref" : "NetACL"}, "Protocol" : -1, "RuleAction" : "Allow", "RuleNumber" : 100 } }, ...
EC2 SecurityGroup
Тут сейчас основные правила доступа, разрешающие SSH, HTTP/S, ICMP для офиса и домашней сетей:
... "EC2SecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", "Properties" : { "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "sg"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ], "VpcId" : {"Ref" : "VPC"}, "GroupDescription" : "Enable access to the Prometheus", "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "JenkinsIP"}, "Description": "Jenkins/Ansible SSH allow" }, { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "HomeAllowLocation"}, "Description": "Home SSH allow" }, { "IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "OfficeAllowLocation"}, "Description": "Office SSH allow" }, { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : { "Ref" : "HomeAllowLocation"}, "Description": "All HTTP" }, { "IpProtocol" : "tcp", "FromPort" : "443", "ToPort" : "443", "CidrIp" : { "Ref" : "HomeAllowLocation"}, "Description": "All HTTPS" }, { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0", "Description": "All HTTP" }, { "IpProtocol" : "tcp", "FromPort" : "443", "ToPort" : "443", "CidrIp" : { "Ref" : "OfficeAllowLocation"}, "Description": "All HTTPS" }, { "IpProtocol" : "icmp", "FromPort" : "8", "ToPort" : "-1", "CidrIp" : { "Ref" : "HomeAllowLocation"}, "Description": "Home ping" }, { "IpProtocol" : "icmp", "FromPort" : "8", "ToPort" : "-1", "CidrIp" : { "Ref" : "OfficeAllowLocation"}, "Description": "Office ping" } ] } }, ...
"CidrIp" : { "Ref" : "OfficeAllowLocation"}
, ссылается на параметр OfficeAllowLocation
:
... "OfficeAllowLocation": { "Description": "The office IP address range that can be used to SSH to the EC2 instances", "Type": "String", "MinLength": "9", "MaxLength": "18", "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.", "Default": "0.0.0.0/0" }, ...
И аналогично — остальные сети.
EBS
Отдельно создаётся
... "EC2DataEBS": { "Type":"AWS::EC2::Volume", "Properties" : { "AvailabilityZone" : {"Ref" : "AvalabilityZone"}, "Size" : {"Ref" : "EBSsize"}, "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "data-ebs"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ], "VolumeType" : "gp2" } }, ...
EC2
И, наконец-то — сам
... "EC2Instance" : { "Type" : "AWS::EC2::Instance", "Properties" : { "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "ec2"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ], "InstanceType" : { "Ref" : "EC2InstanceType" }, "SecurityGroupIds" : [ { "Ref": "EC2SecurityGroup" } ], "KeyName" : { "Ref" : "KeyName" }, "ImageId" : { "Ref" : "AMIID" }, "AvailabilityZone": {"Ref" : "AvalabilityZone"}, "SubnetId": { "Ref" : "PublicSubnet" }, "Volumes" : [ { "VolumeId" : { "Ref" : "EC2DataEBS" }, "Device" : "/dev/xvdb" } ], "SourceDestCheck": "false" } }, ...
Собственно — всё.
Далее разворачивается стек, используя скрипт выше — и можно приступать к Jenkins и Absible.