Задача – развернуть мониторинг 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 предоставляет только одну готовую политику – AWSCloudFormationReadOnlyAccess, поэтому создадим свою, с полным доступом к CloudFormation API вызовам и ресурсам.
Переходим в 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 (рабочая машинка совсем новая):
[simterm]
$ sudo apt install awscli
[/simterm]
Далее настраиваем именованный профиль:
[simterm]
$ aws configure –profile btrm-mon
AWS Access Key ID [None]: AKI***OAA
AWS Secret Access Key [None]: 8fq***90o
Default region name [None]: eu-west-1
Default output format [None]: json
[/simterm]
Пробуем проверить стеки:
[simterm]
$ aws --profile btrm-mon cloudformation list-stacks { "StackSummaries": [] }
[/simterm]
ОК.
Создание тестового стека
Используем шаблон с одним EC2 отсюда ec2_simple_stack.json.
Создаём стек, используя профайл btrm-mon:
[simterm]
$ aws –profile btrm-mon cloudformation create-stack –stack-name btrm-test –template-body file://ec2_simple_stack.json –parameters ParameterKey=AllowLocation,ParameterValue=194.183.169.26/32
[/simterm]
Проверяем:
[simterm]
$ aws –profile btrm-mon cloudformation describe-stacks –stack-name btrm-test –query ‘Stacks[*].StackStatus’ –output text
CREATE_COMPLETE
[/simterm]
ОК, удаляем его, и переходим к скрипту:
[simterm]
$ aws –profile btrm-mon cloudformation delete-stack –stack-name btrm-test
[/simterm]
Скрипт
Сам скрипт доступен в репозитории>>>, тут кратко.
Принимает три обязательных опции с аргументами – имя профайла AWS CLI, имя стека и путь к файлу шаблона:
[simterm]
$ ./create_aws_stack.sh -h
Used to create AWS CLoudFormation stack.
Usage:
-p: (mandatory) AWS CLI profile name for authorization
-s: (mandatory)CloudFormation stack name to be created/updated
-t: (mandatory) path to a template file
[/simterm]
При запуске – скрипт выполняет валидацию шаблона (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 } ...
Пример выполнения скрипта:
[simterm]
$ ./create_aws_stack.sh -p btrm-mon -s btrm-test -t ../../setevoy-aws-templates/ec2_simple_stack.json Validating template ../../setevoy-aws-templates/ec2_simple_stack.json... { "Parameters": [ { "ParameterKey": "AMIID", "DefaultValue": "ami-8a745cf3", "Description": "Debian AMI ID", "NoEcho": false }, { "ParameterKey": "KeyName", "DefaultValue": "setevoy-test", "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance", "NoEcho": false }, { "ParameterKey": "InstanceType", "DefaultValue": "t2.nano", "Description": "EC2 instance type", "NoEcho": false }, { "ParameterKey": "AllowLocation", "NoEcho": false, "Description": "The IP address range that can be used to SSH to the EC2 instances" }, { "ParameterKey": "EC2AvailabilityZone", "DefaultValue": "eu-west-1a", "Description": "AZ for a EC2", "NoEcho": false } ], "Description": "AWS CloudFormation simple EC2 stack template." } Template OK Checking if stack btrm-test already present... Stack btrm-test not found, running Stack Create. Starting AWS CloudFormation stack creation using: AWS profile: btrm-mon stack name: btrm-test template: ../../setevoy-aws-templates/ec2_simple_stack.json Stack exist: False Are you sure to proceed? [y/n] y Running create or update stack btrm-test... Stack creation started. Use https://us-west-1.console.aws.amazon.com/cloudformation/home?region=us-west-1#/stacks?filter=active&tab=events to check its status.
[/simterm]
CloudFormation шаблон
Теперь можно создавать сам шаблон.
Тут потребуются такие ресурсы:
- EIP: нужен статический IP
- VPC
- subnet
- ACL
- EC2
- EBS – для данных Prometehus/Grafana
Сам шаблон доступен в репозитории тут – vpc_ec2_stack.json.
Кратко его основные ресурсы.
VPC
Все создаваемые ресурсы будут включены в одну 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"} }
используется для создания AWS Resource Group.
PublicSubnet
VPC состоит из одной публичной подсети, в которой будет работать наш EC2 с сервером мониторинга:
... "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"} } ] } }, ...
В параметрах шаблона задаётся единая Availability Zone для всех ресурсов, которая потом ими используется:
... "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
Для доступа в инетрнет из публичной сети – создаётся ресурс Internet Gateway:
... "InternetGateway" : { "Type" : "AWS::EC2::InternetGateway", "Properties" : { "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "igw"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
VPC ACL
В дополнение к EC2 Security Group – имеется отдельный Network Access Control List в VPC:
... "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
Отдельно создаётся EBS, который потом подключается к создаваемому EC2:
... "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
И, наконец-то – сам 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.