diff --git a/README.md b/README.md index 251710b..cf7d56a 100644 --- a/README.md +++ b/README.md @@ -49,81 +49,29 @@ sudo install -m755 /tmp/ebs-bootstrap /usr/local/sbin/ebs-bootstrap ## Documentation - - + + ## 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 - 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`) + + + + -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 ⌛ @@ -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 diff --git a/assets/badges/cloudformation.svg b/assets/badges/cloudformation.svg new file mode 100644 index 0000000..77f836a --- /dev/null +++ b/assets/badges/cloudformation.svg @@ -0,0 +1 @@ +CLOUDFORMATIONCLOUDFORMATION diff --git a/assets/badges/github-wiki.svg b/assets/badges/github-wiki.svg new file mode 100644 index 0000000..e05fa7d --- /dev/null +++ b/assets/badges/github-wiki.svg @@ -0,0 +1 @@ +GITHUB WIKIGITHUB WIKI \ No newline at end of file diff --git a/examples/cloudformation.yml b/examples/cloudformation.yml new file mode 100644 index 0000000..d6a7aa2 --- /dev/null +++ b/examples/cloudformation.yml @@ -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 + 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] diff --git a/examples/systemd.service b/examples/systemd.service new file mode 100644 index 0000000..5c89c5d --- /dev/null +++ b/examples/systemd.service @@ -0,0 +1,14 @@ +[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