Ansible on Private Subnet

TOC

Configuring instances in private subnet to run AWS-ApplyAnsiblePlaybooks document

In the following page, we have introduced four ways to initialize process on an EC2 instance (Amazon Linux 2).

あわせて読みたい
Four ways to initialize Linux instance 【Four ways to initialize a Linux instance】 Consider how to perform the initialization process when an EC2 instance is started. We will cover the following ...

In this article, we will delve into one of those methods, using Ansible. In the previous page, the configuration was that the instance was installed in a public subnet. In this article, we will cover how to run the AWS-ApplyAnsiblePlaybooks document in a configuration where the instance is located in a private subnet. I will introduce the following two patterns.

  • The pattern using a NAT gateway
  • Pattern using VPC endpoints

Environment

Diagram of Ansible on Private Subnet.

EC2 instance 1 is the pattern for running Ansible via a NAT gateway, and EC2 instance 2 is the pattern for running Ansible via VPC endpoints.
The content of the playbook will be the same for both instances, and will be placed in an S3 bucket.

CloudFormation template files

We will build the above configuration using CloudFormation. We have placed the CloudFormation template at the following URL

https://github.com/awstut-an-r/awstut-fa/tree/main/025

Explanation of key points of template files

This page only describes the resources related to AWS-ApplyAnsiblePlaybooks document. For more information about the basics of running AWS-ApplyAnsiblePlaybooks documentation, configuration for NAT gateways, and how to run yum in a private subnet, see the following page

あわせて読みたい
Four ways to initialize Linux instance 【Four ways to initialize a Linux instance】 Consider how to perform the initialization process when an EC2 instance is started. We will cover the following ...
あわせて読みたい
yum/dnf on private subnet instances 【Configuration for running yum/dnf on instance in private subnet】 We will check how to run yum/dnf on an instance in a private subnet. In this case, the fo...

AWS-ApplyAnsiblePlaybooks Document Execution (1) (via NAT gateway)

First, let’s check the configuration for running AWS-ApplyAnsiblePlaybooks document via NAT gateway. Define the SSM related resources in fa-025-ssm.yaml.

<meta charset="utf-8">Resources:
  ApplyAnsiblePlaybooksAssociation1:
    Type: AWS::SSM::Association
    DependsOn:
      - RunShellScriptAssociation
    Properties:
      AssociationName: !Sub ${Prefix}-apply-ansible-playbook-association1
      Name: AWS-ApplyAnsiblePlaybooks
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref PlaybookBucket
          OutputS3KeyPrefix: !Sub "${Prefix}/playbook-association-log"
      Parameters:
        Check:
          - "False"
        ExtraVariables:
          - SSM=True
        InstallDependencies:
          - "True"
        PlaybookFile:
          - !Ref PlaybookFileName
        SourceInfo:
          - !Sub '{"path": "https://${PlaybookBucket}.s3.${AWS::Region}.amazonaws.com/${Prefix}/${PlaybookPackageName}"}'
        SourceType:
          - S3
        Verbose:
          - -v
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance1
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

The settings are the same as those described above when the instance is installed in a public subnet. In this case, the instance is installed in a private subnet, but since it can access the Internet via a NAT gateway, the same settings as for a public subnet will work.
In this configuration, the setting of particular interest is the InstallDependencies property. This option is used to install packages that are dependencies on running the AWS-ApplyAnsiblePlaybooks document. To begin with, Ansible is not installed on Amazon Linux 2 by default. So enable this option and let Ansible be installed at document runtime.

AWS-ApplyAnsiblePlaybooks Document Execution (2) (via VPC endpoints)

Next, let’s check the configuration for running AWS-ApplyAnsiblePlaybooks document via VPC endpoints.
This time, we will create the following two resources.

  • AWS-RunShellScript: to be run to install Ansible.
  • AWS-ApplyAnsiblePlaybooks: To run the contents of the Ansible Playbook.

The reason for defining the process in two separate documents is that the instance is located on a private subnet, so Ansible cannot be successfully installed using the options described above. This is due to the fact that the instance is on a private subnet and thus cannot communicate freely to the Internet.
The workaround for this is to install Ansible with the AWS-RunShellScript document first and then run the AWS-ApplyAnsiblePlaybooks document.

AWS-RunShellScript

Resources:
  RunShellScriptAssociation:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub ${Prefix}-run-shellscript-association
      Name: AWS-RunShellScript
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref PlaybookBucket
          OutputS3KeyPrefix: !Sub "${Prefix}/shellscript-association-log"
      Parameters:
        commands:
          - "sudo amazon-linux-extras install -y ansible2"
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance2
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

Execute the commands for the Ansible installation in the commands property. In this case, we will use Amazon Linux Extras for the installation.

Extras is a mechanism in Amazon Linux 2 to enable the consumption of new versions of application software on a stable operating system that is supported until June 30, 2023.

Examples of extras include Ansible 2.4.2, memcached 1.5, nginx 1.12, Postgresql 9.6, MariaDB 10.2, Go 1.9, Redis 4.0, R 3.4, Rust 1.22.1.

Amazon Linux Extras

AWS-ApplyAnsiblePlaybooks

Resources:
  ApplyAnsiblePlaybooksAssociation2:
    Type: AWS::SSM::Association
    DependsOn:
      - RunShellScriptAssociation
    Properties:
      AssociationName: !Sub ${Prefix}-apply-ansible-playbook-association2
      Name: AWS-ApplyAnsiblePlaybooks
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref PlaybookBucket
          OutputS3KeyPrefix: !Sub "${Prefix}/playbook-association-log"
      Parameters:
        Check:
          - "False"
        ExtraVariables:
          - SSM=True
        InstallDependencies:
          #- "True"
          - "False"
        PlaybookFile:
          - !Ref PlaybookFileName
        SourceInfo:
          - !Sub '{"path": "https://${PlaybookBucket}.s3.${AWS::Region}.amazonaws.com/${Prefix}/${PlaybookPackageName}"}'
        SourceType:
          - S3
        Verbose:
          - -v
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance2
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

Notice the InstallDependencies property. Specify “False”. This is because we have already installed Ansible by running the AWS-RunShellScript document, so there is no need to resolve any dependencies.

(Reference) Ansible Playbook

For your reference, here is the content of the Playbook that we will run.

- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: update yum
      yum: name=*
    - name: install the latest version of Apache
      yum: name=httpd state=latest
    - name: start and enable Apache
      service: name=httpd state=started enabled=yes
    - name: make index.html
      shell: ec2-metadata -i > /var/www/html/index.html
Code language: YAML (yaml)

This is the content of writing the instance ID to index.html after installing Apache. This time, we will access and run this one Playbook via a NAT gateway and via VPC endpoints.

Architecting

We will use CloudFormation to build this environment and check the actual behavior.

Preparing Ansible Playbook

Before creating the CloudFormation stack, we will prepare Ansible. Specifically, we will zip up the Playbook and place it in an S3 bucket. For specific commands, please refer to the aforementioned page.

Create CloudFormation stack and check resources in stacks

Create a CloudFormation stack.
Please refer to the following page for information on how to create a stack and check each stack.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After checking the resources in each stack, the information for the main resources created this time is as follows

  • Instance1: i-0a2d78121ee6dfd1f
  • Instance2: i-057dde56603eebcf4

We will also check the resource creation status from the AWS Management Console. First, we will check the EC2 instances.

The two instances have been created successfully.

You can see that two instances have been created. Next, check the execution status of the SSM document in the SSM State Manager.

The SSM Association has been successfully created for the two instances.

You can see that a total of three associations have been created for the two instances, and the Status is “Success”, which indicates that they were executed successfully.

Operation check (1)

Now that everything is ready, let’s access the instances. First, let’s look at instance 1. We will use the SSM Session Manager to access the instance. For details, please refer to the following page.

あわせて読みたい
Accessing Linux instance via SSM Session Manager 【Configure Linux instances to be accessed via SSM Session Manager】 We will check a configuration in which an EC2 instance is accessed via SSM Session Manag...
% aws ssm start-session --target i-0a2d78121ee6dfd1f

Starting session with SessionId: root-0f62c0c5edc7d8e07

sh-4.2$ 
Code language: Bash (bash)

Access yourself with curl.

sh-4.2$ curl http://localhost
instance-id: i-0a2d78121ee6dfd1f
Code language: Bash (bash)

You can see that the contents of the Ansible Playbook have been successfully executed.
For reference, here is an excerpt from the log of the Playbook execution

Amazon Linux release 2 (Karoo)

...

Installed:
  epel-release.noarch 0:7-14

...

Installed:
  ansible.noarch 0:2.9.27-1.el7

...

PLAY [all] *********************************************************************

TASK [update yum] **************************************************************
...

TASK [install the latest version of Apache] ************************************
...

TASK [start and enable Apache] *************************************************
...

TASK [make index.html] *********************************************************
...

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Code language: plaintext (plaintext)

You can see that we have installed Ansible to resolve the dependencies before executing the contents of the Playbook.

Operation check (2)

Next is instance 2.

% aws ssm start-session --target i-057dde56603eebcf4

Starting session with SessionId: root-083a1d2a50de3e64a

sh-4.2$

sh-4.2$ curl http://localhost
instance-id: i-057dde56603eebcf4
Code language: Bash (bash)

This also returned its own ID, so you can see that the contents of the Ansible Playbook were executed successfully.
Let’s check the runtime logs for this one as well, starting with the AWS-RunShellScript log.

Loaded plugins: extras_suggestions, langpacks, priorities, update-motd

...

Installed:
  ansible.noarch 0:2.9.23-1.amzn2

...

Complete!
Code language: plaintext (plaintext)

Next is the AWS-ApplyAnsiblePlaybooks log, which shows the installation of Ansible as specified in the template file.

PLAY [all] *********************************************************************

TASK [update yum] **************************************************************
...

TASK [install the latest version of Apache] ************************************
...

TASK [start and enable Apache] *************************************************
...

TASK [make index.html] *********************************************************
...

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Code language: plaintext (plaintext)

This one does not install Ansible, but runs the Playbook from the beginning.

Summary

We have now seen how to run the AWS-ApplyAnsiblePlaybooks document in a private subnet.
Specifically, we used either a NAT gateway or VPC endpoints.

TOC