Skip to content

Commit

Permalink
(docs): Improve documentaiton around examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Lasith Koswatta Gamage committed Jan 29, 2024
1 parent 1aa2eef commit e9637c4
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 76 deletions.
97 changes: 21 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,81 +49,29 @@ sudo install -m755 /tmp/ebs-bootstrap /usr/local/sbin/ebs-bootstrap

## Documentation

<a href="https://github.com/reecetech/ebs-bootstrap/wiki" target="_blank">
<img src="https://img.shields.io/badge/github_wiki-%2523121011.svg?style=for-the-badge&logo=github&logoColor=white&color=%23aa82e6">
<a href="https://github.com/reecetech/ebs-bootstrap/wiki">
<img src="assets/badges/github-wiki.svg">
</a>

## Use Cases

### `cloud-init`

By default, `ebs-bootstrap` consumes a configuration file located at `/etc/ebs-boostrap/config.yml`. `cloud-init` can be configured to write a config to this location, using the `write_files` module. Ensure that `ebs-bootstrap` is installed on your Instance via the process of baking it into your Golden AMI or downloading it early in the boot process, using the `bootcmd` module.

The advent of Instance Store provided Nitro-enabled EC2 instances the ability to harness the power of high speed NVMe. For a stateful workload like a database, you might want a fast and ephemeral space for temporary tables, alongside a stateful EBS volume declared in a different CloudFormation Stack. However, these Instance Store devices were ephemeral and had to be formatted and mounted on each startup cycle.

From the perspective of a **sceptical** Platforms Engineer, you do not mind a tool like `ebs-bootstrap` automating the task of formatting and mounting an ephemeral device. However, you personally draw the line on automation executing modifications to a stateful device, **without** the prior consent of a human. `ebs-bootstrap` empowers this Platform Engineer by allowing them to specify the execution mode, on a **device-by-device** basis: Instance Store (`force`) and EBS Volume (`healthcheck`)

```yaml
Parameters:
LatestUbuntuAmi:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/canonical/ubuntu/server/20.04/stable/current/amd64/hvm/ebs-gp2/ami-id

Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
...
ImageId: !Ref LatestUbuntuAmi
InstanceType: m5ad.large # Nitro Instance Type
Volumes:
- Device: /dev/sdb # EBS Volume
VolumeId: !ImportValue StatefulVolumeId
BlockDeviceMappings:
- DeviceName: /dev/sdh # Instance Store
VirtualName: ephemeral0
UserData:
Fn::Base64: !Sub
- |+
#cloud-config
write_files:
- content: |
devices:
/dev/sdb:
fs: ${FileSystem}
mountPoint: /mnt/ebs
mountOptions: ${MountOptions}
user: ubuntu
group: ubuntu
permissions: 755
label: stateful
mode: healthcheck
/dev/sdh:
fs: ${FileSystem}
mountPoint: /mnt/instance-store
mountOptions: ${MountOptions}
user: ubuntu
group: ubuntu
permissions: 755
label: ephemeral
mode: force
path: /etc/ebs-bootstrap/config.yml
bootcmd:
- curl -L -o /tmp/ebs-bootstrap "${EbsBootstrapUrlPrefix}-$(uname -m)"
- install -m755 /tmp/ebs-bootstrap /usr/local/sbin/ebs-bootstrap
runcmd:
- /usr/local/sbin/ebs-bootstrap
mounts:
- [ "LABEL=stateful", /mnt/ebs, ${FileSystem}, "${MountOptions}", "0", "2"]
- FileSystem: ext4
MountOptions: defaults,nofail,x-systemd.device-timeout=5
EbsBootstrapUrlPrefix: https://github.com/reecetech/ebs-bootstrap/releases/latest/download/ebs-bootstrap-linux
```
From the perspective of a **sceptical** Platforms Engineer, you do not mind delegating the task of formatting and mounting ephemeral block devices to `ebs-bootstrap`. However, you personally draw the line on automation executing modifications to a stateful device, **without** the prior consent of a human. `ebs-bootstrap` empowers this Platform Engineer by allowing them to specify the execution mode, on a **device-by-device** basis: Instance Store (`force`) and EBS Volume (`healthcheck`)

<a href="https://github.com/reecetech/ebs-bootstrap/blob/main/examples/cloudformation.yml">
<img src="assets/badges/cloudformation.svg">
</a>

Assuming this is the very first launch, `ebs-bootstrap` would refuse to perform any modifications associated to the EBS device as it was assigned the `healthcheck` mode. However, we can temporarily override this behaviour with the `-mode=prompt` option. This allows the Platform Engineer to approve any suggested changes by `ebs-bootstrap`.
> This CloudFormation template demonstrates the installation and configuration of `ebs-bootstrap` on an **Ubuntu Nitro EC2 Instance**. The instance has both an EBS and an Instance Store Volume attached, and the setup is performed using `cloud-init`.
On the first launch, `ebs-bootstrap` would refuse to perform any modifications to the EBS volume as it was assigned the `healthcheck` mode. However, we can temporarily override this behaviour with the `-mode=prompt` option. This allows the Platform Engineer to approve any suggested changes by `ebs-bootstrap`.

```
[~] sudo /usr/local/sbin/ebs-bootstrap -mode=prompt
[~] sudo ebs-bootstrap -mode=prompt
🔵 /dev/nvme1n1: Detected Nitro-based AWS NVMe device => /dev/sdb
🔵 /dev/nvme2n1: Detected Nitro-based AWS NVMe device => /dev/sdh
🟠 Formatting larger disks can take several seconds ⌛
Expand Down Expand Up @@ -169,35 +117,32 @@ LABEL=stateful /mnt/ebs ext4 defaults,nofail,x-systemd.device-timeout=5,comment=

### `systemd`

A potential way of operating `ebs-bootstrap` is through a `systemd` service. This is so we can configure it as a `oneshot` service type that executes after the file system is ready and `clout-init.service` writes any config files to disk. The latter is essential as `ebs-bootstrap` consumes a config file that is located at `/etc/ebs-boostrap/config.yml` by default.
`ExecStopPost=-...` can point towards a script that is executed when the `ebs-bootstrap` service exits on either success or failure. This is a suitable place to include logic to notify an individual that the configured devices failed their relevant healthchecks and the underlying application failed to launch in the process.
One way to utilise ebs-bootstrap is by employing a **oneshot** `systemd` service. This approach allows us to activate `ebs-bootstrap` during system startup, guaranteeing that any EBS or Instance Store volumes are formatted and mounted whenever the system is rebooted.

```ini
[Unit]
Description=EBS Bootstrap
After=local-fs.target cloud-init.service
Description=ebs-bootstrap
After=local-fs.target cloud-init.service # Run after write_files module in cloud-init.service

[Service]
Type=oneshot
RemainAfterExit=true
StandardInput=null
StandardInput=null # Disables stdin to error on prompt
ExecStart=/usr/local/sbin/ebs-bootstrap
PrivateMounts=no
MountFlags=shared
ExecStopPost=-/etc/ebs-bootstrap/post-hook.sh
PrivateMounts=no # Prevents private mount namespaces
MountFlags=shared # Shares mounts to other processes

[Install]
WantedBy=multi-user.target
```

It is then possible to configure another `systemd` service to only start if the `ebs-bootstrap` service is successful. Certain databases support the ability to spread database chunks across multiple devices that need to be mounted to pre-defined directories with the correct ownership and permissions.
It is then possible to configure another `systemd` service to **only** start if the `ebs-bootstrap` service is successful. Certain databases support the ability to spread database chunks across multiple block devices that need to be mounted to pre-defined directories with the correct ownership and permissions enforced.

In this particular use-case, the database could be configured as a `systemd` service that relies on the `ebs-bootstrap.service` to succeed before attempting to start. This can be achieved by specifiying `ebs-boostrap.service` as a dependency in the `Requires=` and `After=` parameters.
In this particular use-case, the database could be configured as a `systemd` service that relies on the `ebs-bootstrap.service` to succeed before attempting to start. This can be achieved by specifying `ebs-boostrap.service` as a dependency in the `Requires=` and `After=` parameters.

```ini
[Unit]
Description=Example Database
Description=postgres
Wants=network-online.target
Requires=ebs-bootstrap.service
After=network.target network-online.target ebs-bootstrap.service
Expand Down
1 change: 1 addition & 0 deletions assets/badges/cloudformation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/badges/github-wiki.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 131 additions & 0 deletions examples/cloudformation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: |
Deploys an EC2 Instance with an EBS and an Instance Store volume. To
reduce cost of operating this example, we use Graviton-supported Instance
types and AMI
Parameters:
KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: Must be the name of an existing EC2 KeyPair.
AvailabilityZone:
Type: AWS::EC2::AvailabilityZone::Name
Description: Availability Zone where EBS Volume will be created
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: The Subnet ID where the EC2 instance will be launched.
VpcId:
Type: AWS::EC2::VPC::Id
Description: The VPC ID where the EC2 instance will be launched.
NitroInstanceType:
Type: String
Default: c6gd.medium
Description: Instance Type
ConstraintDescription: |
For this example, must be a Nitro Instance Type with at least one Instance Store volume
IngressSshCidr:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
MinLength: 9
MaxLength: 18
Default: 0.0.0.0/0
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.
LatestUbuntuAmi:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/canonical/ubuntu/server/20.04/stable/current/arm64/hvm/ebs-gp2/ami-id

Resources:
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for EC2 instance
VpcId: !Ref VpcId
SecurityGroupIngress:
- CidrIp: !Ref IngressSshCidr
IpProtocol: tcp
FromPort: 22
ToPort: 22
EbsVolume:
Type: AWS::EC2::Volume
Properties:
Size: 10
AvailabilityZone: !Ref AvailabilityZone
DeletionPolicy: Delete
Instance:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref LatestUbuntuAmi
InstanceType: !Ref NitroInstanceType
SecurityGroupIds:
- !Ref SecurityGroup
SubnetId: !Ref SubnetId
Volumes:
- Device: "/dev/sdb" # EBS
VolumeId: !Ref EbsVolume
BlockDeviceMappings:
- DeviceName: "/dev/sdh"
VirtualName: "ephemeral0" # Instance Store volume
UserData:
Fn::Base64: !Sub
- |+
#cloud-config
write_files:
- content: |
devices:
/dev/sdb:
fs: ${FileSystem}
mountPoint: /mnt/ebs
mountOptions: ${MountOptions}
user: ubuntu
group: ubuntu
permissions: 755
label: stateful
mode: healthcheck
/dev/sdh:
fs: ${FileSystem}
mountPoint: /mnt/instance-store
mountOptions: ${MountOptions}
user: ubuntu
group: ubuntu
permissions: 755
label: ephemeral
mode: force
path: /etc/ebs-bootstrap/config.yml
- content: |
[Unit]
Description=ebs-bootstrap
After=local-fs.target cloud-init.service
[Service]
Type=oneshot
RemainAfterExit=true
StandardInput=null
ExecStart=/usr/local/sbin/ebs-bootstrap
PrivateMounts=no
MountFlags=shared
[Install]
WantedBy=multi-user.target
path: /etc/systemd/system/ebs-bootstrap.service
bootcmd:
- curl -L -o /tmp/ebs-bootstrap "${EbsBootstrapUrlPrefix}-$(uname -m)"
- install -m755 /tmp/ebs-bootstrap /usr/local/sbin/ebs-bootstrap
runcmd:
- systemctl daemon-reload
- systemctl enable ebs-bootstrap --now
mounts:
- [ "LABEL=stateful", /mnt/ebs, ${FileSystem}, "${MountOptions}", "0", "2"]
- FileSystem: ext4
MountOptions: defaults,nofail,x-systemd.device-timeout=5
EbsBootstrapUrlPrefix: https://github.com/reecetech/ebs-bootstrap/releases/latest/download/ebs-bootstrap-linux

Outputs:
InstanceId:
Description: Instance Id of the newly created EC2 instance
Value: !Ref Instance
PrivateIP:
Description: Private IP address of the newly created EC2 instance
Value: !GetAtt [Instance, PrivateIp]
Loading

0 comments on commit e9637c4

Please sign in to comment.