Deploy a Puppet Master and Puppet agents on Amazon EC2 using Terraform
Puppet is one of the most widely used configuration management and automation systems. This blog post shows an automated solution using Terraform to deploy Puppet Open Source to AWS.
The solution provisions a Puppet Master in a highly available setup, registers multiple nodes to the Puppet Master automatically, and configures a sample r10k control repository to manage the Puppet environment. All instances are based on Amazon Linux 2.
Here is the solution architecture:
To make sure the Puppet Master is highly available and self-healing, we put the master node in an auto-scaling group (ASG) and use EFS to store the main Puppet configurations (/etc/puppetlabs). When the Puppet Master node is down, the auto-scaling group will spin up a new Puppet Master with the same configurations, and the new master will mount the EFS to get the certificates and configurations of the previous master. This way, the puppet nodes can connect to the new master automatically without regenerating certificates.
Puppet nodes use the master hostname to locate the master instance. To make sure the Puppet nodes can resolve the master hostname, the solution also provisions a record set for the Puppet Master instance in a Route 53 private zone. This record set maps the Puppet Master hostname to its private IP address. The solution also updates the record set with the new IP address after the master node is recovered.
Implementation Details
Now, let’s talk about the detailed implementation and some key considerations of this solution.
Puppet Master
Amazon Linux 2 is a good fit for the Puppet Master since it has the systemd support (amazon Linux v1 doesn’t support systemd) required to bootstrap Puppet. Plus, it comes with many AWS tools (including the AWS CLI), which helps reduce the execution time of the EC2 user data. Here are the steps to set up the Puppet Master node (all these steps are automated using user data on the Puppet Master instance):
1. Mount the EFS Volume to the master node
Mount an EFS volume to the directory /etc/puppetlabs which will be used to store puppet certificates and configurations.
yum install -y amazon-efs-utils
mkdir /etc/puppetlabs
mount -t efs fs-de0ab596:/ ${efs_dns_name}:/ /etc/puppetlabs
Note: If you are not using Amazon Linux 2, please refer to these instructions to mount the EFS volume to your instance.
2. Create/Update the Route 53 DNS record of the Puppet Master node
declare -x INPUT_JSON=$(cat <<EOF
'{
"HostedZoneId": "${hosted_zone_id}",
"ChangeBatch": {
"Comment": "Update the A record set",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "${master_hostname}",
"Type": "A",
"TTL": 300,
"ResourceRecords": [
{
"Value": "$(curl --silent --show-error --retry 3 http://169.254.169.254/latest/meta-data/local-ipv4)"
}
]
}
}
]
}
}'
EOF
)
eval aws route53 change-resource-record-sets --cli-input-json $INPUT_JSON
3. Install Puppet on the master node
If this is the first time the Puppet Master node is launched (the folder /etc/puppetlabs/ is empty), install and configure the Puppet server directly.
- Add puppet repository on both Puppet Master and agent node
- Install Puppet server on the Puppet Master node
- Configure the Puppet Master with the correct certname and dns_alt_name settings, and set autosign to true to sign the node certificate requests automatically. This will update settings in /etc/puppetlabs/puppet/puppet.conf, you can update more settings based on your needs, using commands like “puppet config set …”. For the details about how to configure the parameters in puppet.conf file, please refer to Config files: The main config file (puppet.conf).
# Install puppet server
rpm -Uvh ${puppet_repo}
yum -y install puppetserver
export PATH=/opt/puppetlabs/bin:$PATH
# Configure Puppet master
puppet config set certname ${master_hostname} --section main
puppet config set dns_alt_names puppet,${master_hostname} --section master
puppet config set autosign true --section master
If the Puppet Master is down, and the auto-scaling group spins up another Puppet Master, the EFS volume should keep all files under /etc/puppetlabs, so folder /etc/puppetlabs/ is not empty. In this case, once the new master is up, the user data will back up the /etc/puppetlabs/ folder to /tmp/puppetbackups/, and then install puppet server after the installation is completed, and overwrite /etc/puppetlabs/ with the contents in the backup folder.
# Backup master configurations
mkdir /tmp/puppetbackup
rm -rf /tmp/puppetbackup/*
cp -a /etc/puppetlabs/. /tmp/puppetbackup
# Install puppet server
rpm -Uvh https://yum.puppet.com/puppet5/puppet5-release-el-7.noarch.rpm
yum -y install puppetserver
export PATH=/opt/puppetlabs/bin:$PATH
# Configure Puppet master
puppet config set certname ${master_hostname} --section main
puppet config set dns_alt_names puppet,${master_hostname} --section master
puppet config set autosign true --section master
# Restore master configurations
rm -rf /etc/puppetlabs/*
cp -a /tmp/puppetbackup/. /etc/puppetlabs
4. Start the Puppet Server
# Start the Puppet master and add the service to start up
systemctl start puppetserver
systemctl enable puppetserver
Puppet Agents
Installing and configuring the puppet agents is simpler. For the agents, you only need to install the Puppet repository on the nodes, and then install and start the Puppet service. There is only one configuration that needs to be set to make sure the agent node can register to the correct master node. To do this, set the “server” to the master node host name in the main section of the puppet.conf file:
# Install puppet agent
rpm -Uvh https://yum.puppet.com/puppet5/puppet5-release-el-7.noarch.rpm
yum -y install puppet-agent
export PATH=/opt/puppetlabs/bin:$PATH
# Configure the server name of the agent to the master hostname
puppet config set server ${master_hostname} --section main
# Start puppet agent and add the service to start up
puppet resource service puppet ensure=running enable=true
Set Up the r10k Control Repository
r10k is a tool to help manage dynamic environments. It can handle creating new environments from git branches and deploying a set of modules to that environment. For details on r10k, please see this tutorial.
First, install r10k:
/opt/puppetlabs/puppet/bin/gem install r10k
- Configure r10k with /etc/r10k.yaml. r10k is configured with an /etc/r10k.yaml r10k configuration file (/etc/r10k.yaml). This manages the configuration options for the ‘r10k deploy’ command. The following sample YML file gives the most basic configurations for r10k. Once you’ have written your r10k.yaml file, push it to your git repository. Be sure to set the remote for your git repo in your r10.yaml file:
---
:cachedir: '/var/cache/r10k'
:sources:
:base:
remote: '${r10k_repo}'
basedir: '/etc/puppetlabs/code/environments'
- Finally, run r10k:
r10k deploy environment -p
Note: If the git repository is private, you will need to manage the RSA public and private keys for the Puppet Master’s connection to the repository with the r10k.yaml file. Saving RSA keys in the repository is not safe. Therefore, I suggest using AWS Systems Manager Parameter Store or AWS Secrets Manager to keep the RSA keys safe and use user data to retrieve the keys. If you are using the AWS Systems Manager Parameter Store, you need to make sure the RSA keys are added to the Parameter Store using the AWS CLI. If you upload the keys using the AWS console, it will replace the new line characters with spaces, and the RSA private key won’t work. Use the below commands to add your RSA keys into parameter store:
aws ssm put-parameter --name "rsa_private_key_name" --type "String" --value file:///root/.ssh/id_rsa --region us-east-1
aws ssm put-parameter --name "rsa_public_key_name" --type "String" --value file:///root/.ssh/id_rsa.pub --region us-east-1
Then, you can retrieve the RSA public and private keys and save them to the Puppet Master node:
rsa_private_key=$(/usr/local/bin/aws ssm get-parameters --names ${rsa_private_key_name} --with-decryption --region ${aws_region} | jq -r '.Parameters[0].Value')
rsa_public_key=$(/usr/local/bin/aws ssm get-parameters --names ${rsa_public_key_name} --with-decryption --region ${aws_region} | jq -r '.Parameters[0].Value')
cat > /root/.ssh/id_rsa <<- EOM $rsa_private_key EOM cat > /root/.ssh/id_rsa.pub <<- EOM
$rsa_public_key
EOM
chmod 400 /root/.ssh/id_rsa
chmod 400 /root/.ssh/id_rsa.pub
Deploy and Test the solution
You can fork or download the solution from https://github.com/1Strategy. In that repo, create a terraform.tfvars file, with the below parameters initialized:
Parameters | Details | Default Value |
---|---|---|
aws_access_key | aws_access_key of your IAM user | N/A |
aws_secret_key | aws_secret_key of your IAM user | N/A |
ssh_key_name | The ssh key name which will be used to log on to master | N/A |
r10k_repository | r10k control repository url | N/A |
puppet_repository | puppet rpm package url to be installed | https://yum.puppet.com/puppet5/puppet5-release-el-7.noarch.rpm |
vpc_id | vpc which will holds the Puppet Master | N/A |
subnet_id | the subnet of the Puppet Master will be launched in | N/A |
instance_type | EC2 instance type of the Puppet Master | t2.medium |
puppet_master_name | The Puppet Master name which will be part of hostname | puppet.master |
aws_route53_zone_name | Route53 private host zone name | private |
asg_min | For single master, set it to “1” | 1 |
asg_max | For single master, set it to “1” | 1 |
Note: The instance type must be at least a t2.medium. Otherwise, you may get out of memory errors, and the master won’t work.
Then go to the terraform folder and deploy the solution with below commands:
terraform init
terraform plan
terraform apply
If everything works correctly, when you log on to the master server, /etc/puppetlabs/code/environments/production/ should exist with the files from r10k control repository:
[root@puppet production]# ls
data hiera.yaml manifests README.md site
environment.conf LICENSE Puppetfile scripts
If you log on to the Puppet agent node, and type “puppet agent –test” as root user, the Puppet agent should be able to retrieve the catalog successfully:
[root@puppet-agent-hostname ~]# puppet agent --test
Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Retrieving locales
Info: Caching catalog for puppet-agent-hostname
Info: Applying configuration version 'puppet.master.private-production-78662f908d0'
Notice: Applied catalog in 0.01 seconds
Conclusion
This solution deploys Open Source Puppet version 5.5.1 by default via Terraform. Open Source Puppet is available to download. If you have further questions, feel free to schedule a consultation with us or email me directly at jing@new.1strategy.com.