Задача — развернуть мониторинг 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.









