Задача – добавить ресурсы AWS Elastic File System в существующий CloudFormation стек.
В CloudFormation для этого имеется ресурс AWS::EFS::FileSystem
, который и используем.
Шаблон для CloudFormation уже создан, и в примерах ниже будут отсылки к его ресурсам.
Содержание
Опции EFS
Перед тем, как создавать EFS – немного рассмотрим доступные опции.
EFS имеет два основных доступных параметра – Performance mode (“тип производительности”), и Throughput mode (“пропускной режим”).
Performance mode
Performance mode не влияет на стоимость и не может быть изменён после создания EFS.
В свою очередь Performance mode имеет два типа – General Purpose Performance Mode и Max I/O Performance Mode.
При использовании General Purpose Performance Mode вы получаете стабильные время ответа от файловой системы и скорость передачи данных, но ограничены 7000 операциями в секунду для всех подключенных к EFS клиентов.
Использование типа Max I/O – вы получите больше I/O в секунду и пропускную способность, но время операций чтения/записи может быть незначительно выше.
См. Limits for Amazon EFS File Systems и Performance Modes.
Так же есть достаточно интересный пост тут>>>.
Основной совет при выборе типа производительности – создать EFS с типом General Purpose, нагрузить её до планируемой в Production нагрузки, и проверить значения PercentIOLimit
в CloudWatch: если значение достигает 100% – то стоит попробовать Max I/O.
Throughput Modes
Кроме Performance mode, который отвечает за кол-во операций в секунду и время выполнения этих операций – имеет значение и пропускная возможность EFS, которая ограничивает скорость передачи от хостов EFS к клиентам.
Throughput Modes также имеет два типа – Bursting и Provisioned.
В Bursting типе скорость передачи растёт пропорциональность размеру EFS, а в Provisioned задаётся фиксированным значением при создании EFS (и может быть изменена после создания).
При использовании Bursting типа сразу после создания EFS, пока используемый размер менее 10 GB – вы получаете 0.5 мб/с постоянной скорости передачи данных, и возможность временно увеличить скорость до 100 мб/с на 72 минуты в день (!):
File System Size (GiB) | Baseline Aggregate Throughput (MiB/s) | Burst Aggregate Throughput (MiB/s) | Maximum Burst Duration (Min/Day) | % of Time File System Can Burst (Per Day) |
---|---|---|---|---|
10 | 0.5 | 100 | 7.2 | 0.5% |
256 | 12.5 | 100 | 180 | 12.5% |
512 | 25.0 | 100 | 360 | 25.0% |
1024 | 50.0 | 100 | 720 | 50.0% |
1536 | 75.0 | 150 | 720 | 50.0% |
2048 | 100.0 | 200 | 720 | 50.0% |
3072 | 150.0 | 300 | 720 | 50.0% |
4096 | 200.0 | 400 | 720 | 50.0% |
Т.е., чем больше будет размер EFS – тем выше будет постоянная скорость доступа и дольше возможность её увеличения.
Для мониторинга доступной скорости используются Bust Credits – BurstCreditBalance
в CloudWatch.
При использовании Provisioned Mode – вы можете задать скорость доступа независимо от кол-ва данных в EFS, но при этом будете оплачивать как за GB в EFS, так и за пропускной канал.
См. детали в pricing.
CloudFormation
Т.к. у нас никаких особых требований к EFS нет – то будем использовать дефолтные General Purpose Performance Mode и Bursting Throughput Mode.
Для создания EFS в CloudFormation потребуется три ресурса:
AWS::EC2::SecurityGroup
: для ограничения доступа к EFSAWS::EFS::MountTarget
: точка доступа к EFS, со своим IP/URL, документация тут>>>AWS::EFS::FileSystem
: собственно, сам EFS
Приложение состоит из трёх ЕС2 – два для самого приложения, которые обслуживают клиентов и доступны через Application Load Balancer, это App1 и App2, и третий инстанс – Console, на котором работают cron-задачи, RabbitMQ, Redis и т.д.
Каждый EC2 находится в отдельной подсети.
IAM пользователь, от которого создаётся EFS, должен иметь права AmazonElasticFileSystemFullAccess
.
AWS::EC2::SecurityGroup
Добавляем Security Group, в которой разрешаем доступ к порту 2049 для всех EC2 из SG AppSecurityGroup.
AppSecurityGroup содержит правила для доступа к EC2, и все создаваемые EC2 будут подключены к ней:
... "AppSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties" : { "GroupDescription" : "Backend server access", "VpcId" : { "Ref": "VPC" }, "SecurityGroupIngress" : [ { "Description": "Allow SSH from Office1", "IpProtocol" : "tcp", "FromPort" : 22, "ToPort" : 22, "CidrIp" : { "Ref": "OfficeAllowLocation1" } }, ...
Добавляем ресурс MountTargetSecurityGroup
, в которой используем ID AppSecurityGroup:
... "MountTargetSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "VpcId": { "Ref": "VPC" }, "GroupDescription": "Security group for mount target", "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": "2049", "ToPort": "2049", "SecurityGroupIngress": { "Ref": "AppSecurityGroup" } } ] } }, ...
AWS::EFS::MountTarget
Добавляем создание точек монтирования, которые используют добавленную выше Security Group и подсети, которые уже имеются в шаблоне.
Для каждого инстанса создаётся своя подсеть, поэтому создаём три Mount Target-а:
... "App1MountTarget": { "Type": "AWS::EFS::MountTarget", "Properties": { "FileSystemId": { "Ref": "ReceiptsFileSystem" }, "SubnetId": { "Ref": "App1Subnet" }, "SecurityGroups": [ { "Ref": "MountTargetSecurityGroup" } ] } }, "App2MountTarget": { "Type": "AWS::EFS::MountTarget", "Properties": { "FileSystemId": { "Ref": "ReceiptsFileSystem" }, "SubnetId": { "Ref": "App2Subnet" }, "SecurityGroups": [ { "Ref": "MountTargetSecurityGroup" } ] } }, "ConsoleMountTarget": { "Type": "AWS::EFS::MountTarget", "Properties": { "FileSystemId": { "Ref": "ReceiptsFileSystem" }, "SubnetId": { "Ref": "ConsoleSubnet" }, "SecurityGroups": [ { "Ref": "MountTargetSecurityGroup" } ] } }, ...
При монтировании “шары” на каждом EC2 будет использоваться общий URL для EFS, который будет резолвиться в IP Mount Target-а в Availability зоне каждого инстанса.
AWS::EFS::FileSystem
Добавляем создание самой EFS:
... "ReceiptsFileSystem": { "Type": "AWS::EFS::FileSystem", "Properties": { "PerformanceMode": "generalPurpose", "FileSystemTags": [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ {"Ref" : "AWS::StackName"}, "receipts-efs"] ] } }, {"Key" : "Env", "Value" : {"Ref" : "ENV"} } ] } }, ...
Outputs
Для подключения этой EFS к инстансам – используем DNS-имя, которое создаётся из efs-ID + .efs.us-east-2.amazonaws.com.
Для этого – выведем в Outputs данные, которые потом будем использовать в Ansible:
... "ReceiptsFileSystemDNS" : { "Description" : "EFS DNS name to mount", "Value" : { "Fn::Join": [ ".", [ { "Ref": "ReceiptsFileSystem" }, "efs.us-east-2.amazonaws.com" ]] } }, ...
Запускаем создание/апдейт стека:
Ansible
Следующим шагом – созданную EFS надо смонтировать к создаваемым EC2.
Добавим роль efs
:
[simterm]
$ mkdir -p roles/efs/{tasks,vars}
[/simterm]
В роли нам потребуется:
- создать каталог
/storage
- установить пакет
nfs-common
- смонтировать EFS, используя DNS-имя в каталог
/storage
Монтирование EFS описано тут>>>.
Создаём файл roles/efs/tasks/main.yml
:
- name: "Read ENV specific variables" include_vars: file: "{{ env }}_vars.yml" - name: "Install nfs-common package" apt: name: "nfs-common" state: present - name: "Create {{ efs_mount_path }} directory" file: path: "{{ efs_mount_path }}" state: directory mode: 0755 recurse: yes - name: "Mount EFS {{ efs_dns_name }} to the {{ efs_mount_path }}" mount: path: "{{ efs_mount_path }}" src: "{{ efs_dns_name }}" state: mounted fstype: nfs4 opts: "nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport"
Добавляем два файла переменных: в roles/efs/vars/main.yml
будут общие переменные, такие как имя каталога для точки монтирования, а в roles/efs/vars/{{ env }}_vars.yml
– DNS имя EFS.
Для каждого окружения в инвентори-файле задаётся переменная {{ env }}
, используя которую можно переопределить файл с параметрами для каждого окружения:
[simterm]
$ cat hosts.ini | grep -w env env=test env=dev env=production
[/simterm]
Файл roles/efs/vars/main.yml
:
efs_mount_path: "/storage"
И файл roles/efs/vars/test_vars.yml
– тут задаём ReceiptsFileSystemDNS
из Outputs стека + :/
в конце:
efs_dns_name: "fs-***.efs.us-east-2.amazonaws.com:/"
Добавляем вызов роли в плейбук ко всем остальным ролям:
... - role: common tags: app - role: efs tags: app - role: exim tags: app ...
Запускаем:
[simterm]
... PLAY [all] **** TASK [Gathering Facts] **** ok: [test.app2.mobilebackend.domain.tld] ok: [test.console.mobilebackend.domain.tld ok: [test.app1.mobilebackend.domain.tld] TASK [efs : Read ENV specific variables] **** ok: [test.app1.mobilebackend.domain.tld] ok: [test.app2.mobilebackend.domain.tld] ok: [test.console.mobilebackend.domain.tld] TASK [efs : Install nfs-common package] **** ok: [test.console.mobilebackend.domain.tld] ok: [test.app2.mobilebackend.domain.tld] ok: [test.app1.mobilebackend.domain.tld] TASK [efs : Create /storage directory] **** ok: [test.app2.mobilebackend.domain.tld] ok: [test.console.mobilebackend.domain.tld] ok: [test.app1.mobilebackend.domain.tld] TASK [efs : Mount EFS fs-***.efs.us-east-2.amazonaws.com:/ to the /storage] **** changed: [test.app2.mobilebackend.domain.tld] changed: [test.console.mobilebackend.domain.tld] changed: [test.app1.mobilebackend.domain.tld] PLAY RECAP **** test.app1.mobilebackend.domain.tld : ok=5 changed=1 unreachable=0 failed=0 test.app2.mobilebackend.domain.tld : ok=5 changed=1 unreachable=0 failed=0 test.console.mobilebackend.domain.tld : ok=5 changed=1 unreachable=0 failed=0 Provisioning done.
[/simterm]
Проверяем на инстансе App1:
[simterm]
root@ip-10-0-6-12:/home/admin# ls -l /storage/ total 0
[/simterm]
Создаём тестовый файл:
[simterm]
root@ip-10-0-6-12:/home/admin# touch /storage/test.txt
[/simterm]
Проверяем на инстансе App2:
[simterm]
root@ip-10-0-6-21:/home/admin# ls -l /storage/ total 4 -rw-r--r-- 1 root root 0 Aug 1 12:07 test.txt
[/simterm]
Готово.