<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>inventory on Digi Hunch</title><link>https://static.digihunch.com/tag/inventory/</link><description>Recent content in inventory on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 10:15:02 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/inventory/index.xml" rel="self" type="application/rss+xml"/><item><title>AWS Systems Manager is an Omnipotent Hodgepodge</title><link>https://static.digihunch.com/2023/10/the-systems-manager-hodgepodge/</link><pubDate>Sun, 29 Oct 2023 21:32:49 -0400</pubDate><guid>https://static.digihunch.com/2023/10/the-systems-manager-hodgepodge/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-ssm.webp" alt="Featured image of post AWS Systems Manager is an Omnipotent Hodgepodge" /&gt;&lt;h2 class="wp-block-heading"&gt;Introduction to Systems Manager&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS Systems Manager addresses a lot of SysOps requirements for configuration management, including server automation. In this domain, there is another AWS service called OpsWorks. However, with OpsWorks Stack, OpsWorks Chef and OpsWorks Puppet all coming EOL in 2024, the entire OpsWorks service is mostly deprecated. By partnering with leaders such as Chef and Puppet, OpsWork services represent the era when AWS needed to mirror the configuration management capability on premise, in an effort to convince customers migrating to the cloud. Today, AWS Systems Manager has evolved to fill a lot of gaps around configuration management for servers in the cloud. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="530" src="https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-1.webp" alt="" class="wp-image-12960" srcset="https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-1.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-1-300x155.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-1-768x398.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although AWS Systems Manager sounds like a single service. It consists a collection of many seemingly disparate capabilities that serves similar requirements around configuration management. In fact, many of the Systems Manager capabilities are built on top of a couple of what I call core capabilities, such as Session Manager, RunCommand, Automation. This post will review these core capabilities and how Systems Manager employs them to expand with other capabilities.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;SSM Agent and Session Manager&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;What enables all other capabilities is the SSM agent installed on the EC2 instances. The agent running as a systemctl task by ssm-user on EC2 instances. Most of AMIs come with this agent pre-installed. It stores the logs in /var/log/amazon/ssm/. This agent works with an instance profile with a role with the AmazonSSMManagedInstanceCore managed policy, in order to communicate with AWS Systems Manager (ssm.&amp;lt;region&amp;gt;.amazonaws.com) backend. Because of that, you also need to provide a network path to the backend endpoint, either via Internet, or interface endpoint. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This communication also allows an IAM user to connect to an instance&amp;#8217;s shell. A common use case is for private instance that do not have Internet access but do have access to SSM backend endpoint. In &lt;a href="https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;a previous post&lt;/a&gt; I discussed using Session Manager to replace a bastion host to connect to EKS nodes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When launching an instance using an AMI with SSM pre-installed, the SSM agent should launch after all the config sets from &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html"&gt;Cloudformation Init&lt;/a&gt; are finished. As a result, the Cloudformation Init script is not able to communicate with SSM backend via the agent, unless you install and start SSM agent first on your own, in CloudFormation Init. To troubleshoot SSM, it is important to review its &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-agent-logs.html"&gt;logs&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Through Systems Manager Hybrid Activation, the SSM agent can also work on virtual machines out of AWS and report back to with SSM backend. This gives on-prem servers the identities (instance tags, instance profiles) required for Systems Manager to manage them as if they were EC2 instances. As a result, extend Systems Manager capabilities to on-prem fleet (requiring advanced instances tier).&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Types of SSM Documents&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are several &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/documents.html"&gt;types&lt;/a&gt; of document that SSM uses, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Command Document&lt;/li&gt;&#10;&lt;li&gt;Automation Document&lt;/li&gt;&#10;&lt;li&gt;Package Document&lt;/li&gt;&#10;&lt;li&gt;Session Document&lt;/li&gt;&#10;&lt;li&gt;Policy Document&lt;/li&gt;&#10;&lt;li&gt;Change Calendar Document&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The AWS documentation has a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/documents.html#what-are-document-types"&gt;table&lt;/a&gt; on what they each are for. Here I&amp;#8217;ll focus on three types of documents: Command Document, Automation Document and Session Document.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Command Document is for the RunCommand capability. It executes on EC2 instances usually performing tasks relating to the operating system or application. I think of a Command Document as an Ansible Playbook that consists of Ansible tasks. We can author Command document that runs configuration steps using &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/documents-command-ssm-plugin-reference.html"&gt;plugins&lt;/a&gt;, such as &lt;code&gt;aws:downloadContents&lt;/code&gt;, &lt;code&gt;aws:runShellScript&lt;/code&gt;, etc. This feature directly competes with Ansible. To troubleshoot why a command fail on an instance, check the file &lt;code&gt;ssm-document-worker.log&lt;/code&gt; in the ssm agent log directory. Each log entry should have a command ID as reference.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Automation Document (aka runbooks) is for the Automation Capability. You can define sequence of actions for automation. There are many pre-defined &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-actions.html"&gt;actions &lt;/a&gt;such as executing AWS API calls (&lt;code&gt;aws:executeAwsApi&lt;/code&gt;), run commands (&lt;code&gt;aws:runCommand&lt;/code&gt;), or executing a Lambda function. Therefore a runbook requires an IAM role (Automation Role). The schema of action sequence (YAML or JSON) looks very similar to an Ansible playbook. The web console comes with an UI to visualize the action sequence but most of the time I&amp;#8217;d rather . &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Session Document is for Session Manager capability. AWS Systems Manager Session Manager uses Session documents to determine which type of session to start, such as a standard session, a port forwarding session, or a session to run an interactive command. In most cases, automation developers do not need to create their own Session document, because the pre-built ones are sufficient:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;AWS-PasswordReset&lt;/li&gt;&#10;&lt;li&gt;AWS-StartInteractiveCommand&lt;/li&gt;&#10;&lt;li&gt;AWS-StartPortForwardingSession&lt;/li&gt;&#10;&lt;li&gt;AWS-StartPortForwardingSessionToSocket&lt;/li&gt;&#10;&lt;li&gt;AWS-StartSSHSession&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In my experience, I use the &lt;code&gt;AWS-StartSSHSession&lt;/code&gt; and &lt;code&gt;AWS-StartPortForwardingSession&lt;/code&gt; documents most often. To establish SSH connection for forward port to connecting host for Remote Desktop session. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To author your own document, reference the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/documents-schemas-features.html#documents-schema-twox"&gt;schema&lt;/a&gt; correctly and use the latest SSM agent. However, I would explore if any existing &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/documents-ssm-sharing.html"&gt;shared document&lt;/a&gt; in the library already covers what you need. For example, the command document AWS-JoinDirectoryServiceDomain help join a Windows server to a managed Active Directory domain. The command document AWS-RunPatchBaseline is used by Systems Manager Patch Manager capability to check and apply operating system patches. They include steps for Windows, MacOS and Linux instances. The automation runbook AWS-AttachIAMToInstance helps you add IAM role to an EC2 instance. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;RunCommand and Automation&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Run Command capability run on top of SSM agent. You can specify one or more target instances. You also specify other other options such as command parameters, rate control and where the output goes. This capability allows an IAM user to run command directly on the OS of an instance (using an OS user ssm-agent) and centrally keep track of those command runs on the AWS side. The most common commands to run on the OS is packaged into Command Documents. There is even a Command Document that allows you to run a pre-built &lt;a href="https://aws.amazon.com/blogs/mt/running-ansible-playbooks-using-ec2-systems-manager-run-command-and-state-manager/"&gt;Ansible playbook&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another way this capability is extremely helpful, is that we can reduce the load of cloud init process. Traditionally, we put a log of logics in the user data script for the cloud init process to execute. The &lt;a href="https://help.ubuntu.com/community/CloudInit"&gt;cloud-init&lt;/a&gt; mechanism comes from Linux OS and the execution of the user data script is not very transparent to troubleshoot. You have to check the cloud-init-output log from the OS. The use of the UserData script should be reserved for establishing communication with CloudFormation endpoint and SSM endpoint. From there, other automation tasks should be done using SSM capabilities (e.g. State Manager) for better manageabilities. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take an example of joining a newly provisioned Windows server to a domain. If we do this in user data script, we will have a few problems. First, we can only tell success/fail state from logs in the OS. Second, if an OS user inadvertently removed the instance from domain, there is no mechanism to capture that. If we use Systems Manager&amp;#8217;s RunCommand capability, along with State Manager association, the AWS management console will be able to tell whether domain joining is successful, and the association can detect when the instance is removed from domain, report this finding as out of compliance, and remediate the issue. We&amp;#8217;ll discuss State Manager in more detail in the next section.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As part of automation, we often have to invoke AWS API calls, which happens outside of any target VMs. The Automation capability of Systems Manager is for this scenario. You can orchestrate your API calls using Automation runbooks. These automation steps do not execute on any target EC2 instance, so they do not rely on SSM agent. However, it needs its own IAM role to perform API tasks. This capability saves you from having to run API calls by creating a new Shell environment to run AWS CLI, or from your own Lambda function using the &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html"&gt;boto3 &lt;/a&gt;SDK library. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we combine Automation and RunCommand capabilities, we can perform most of the automation orchestration steps. They are the core capabilities that further enable a variety of other Systems Manager capabilities.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Maintenance Window and State Manager&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Maintenance window is a very straight forward capability to schedule RunCommand activity with a cron or rate &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/reference-cron-and-rate-expressions.html"&gt;expression&lt;/a&gt;. You can specify target by instance tags, define one or more tasks, and define a window of activity and at what point prior to the end of Windows should the agent stop performing more activities (cutoff). Each task can be a type of a RunCommand command, Step Function, Lambda function and automation runbooks.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;State Manager is a similar capabilities with a lot of feature overlap with Maintenance Window. State Manager operates on the concept of &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/state-manager-associations.html"&gt;associations&lt;/a&gt;. An association connects target instances to command document or automation runbook to execute. Similar to Maintenance Window, you can specify a schedule expression, document parameters and instance tags. State Manager was brought in to combat configuration drift. The associated document should consist of idempotent scripts so that a State Manager association can repeatedly execute these documents to ensure compliance.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Maintenance Window is more about scheduling one or more tasks. On the State Manager side however, association failure by default will be reported as out of compliance compliance. This is useful in scenarios such as keeping a Window instance in the domain, or keeping SSM agent up to date. You can choose either capability for many common setups but they have subtle differences. For example, for Patch management, you can use State Manager to detect missing patches and report compliance, and Maintenance Window to actually apply the missing patches. In fact, there is a document page on &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/state-manager-vs-maintenance-windows.html"&gt;choosing between State Manager and Maintenance Windows&lt;/a&gt; to distinguish their best use cases.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Fleet Manager and Inventory&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Fleet Manager presents a centralized view for all instances for users to perform common administration tasks, such as exploring file systems and logs, admin users and groups, manage registry and events on Windows instances, check processes and performance metrics. It also gives shortcuts to patch nodes, run commands, start session, etc. I think of Fleet Manager as a minimalist configuration management UI. It is not as sophisticated as those from Ansible Tower or Puppet but it comes at no additional cost.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A very useful feature of Fleet Manager is to run a web-based remote desktop to connect to Windows Instances. This saves the need for a bastion host as long as the instances have SSM connection. You will need the RSA private key to decrypt the Administrator password, which I would not recommend. If the Windows server is on a domain, you can enter your domain credential via Fleet Manager. If the users logged in via IAM identity center, Fleet Manager also has the login option for them via SSO using &lt;a href="https://aws.amazon.com/blogs/security/how-to-enable-secure-seamless-single-sign-on-to-amazon-ec2-windows-instances-with-aws-sso/"&gt;IAM Identity Center identity&lt;/a&gt;. When a user logs in this way, Fleet Manager uses RunCommand capability to execute &lt;code&gt;AWSSSO-CreateSSOUser&lt;/code&gt; document against the server to create a local admin user.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another aspect of configuration management is the inventory management. Unlike in Ansible, the term inventory in the context of Systems Manager refers to the metadata of instances, which includes installed applications, AWS components, network configurations, instance details, services, Windows registry and roles, etc. The full list of what is part of metadata is in the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-inventory.html"&gt;document&lt;/a&gt; and you can even define your own inventory item. To gather inventory data, we can makes use of a State Manager association to execute the AWS-GatherSoftwareInventory &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/syman-inventory-troubleshooting.html"&gt;document&lt;/a&gt;. Once we set up the association, the agents will report inventory data back to Systems Manager. More importantly, we can create Resource Data Sync objects to write inventory data (along with compliance data) to S3 buckets, allowing downstream applications to consume. A common use case is to run Athena query against those bucket and produce QuickSight dashboard. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Patch Manager and Compliance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Patch Manager also operates on State Manager associations. The automation runbook is &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-aws-runpatchbaseline.html"&gt;AWSRunPatchBaseline&lt;/a&gt;, where you can just scan for missing patches or install them as well. The SSM document can run on all three platforms (Windows, Linux and MacOS) and determines which patches are missing relative to a the Patch Baseline. There should be at least one default Patch baseline. Each OS (e.g. Ubuntu, Debian, Amazon Linux, etc) classifies patches differently, and a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-predefined-and-custom-patch-baselines.html"&gt;patch baseline&lt;/a&gt; is a configuration that defines whether a patch is approved based on operating system and their classifications. The automation document also allows you to override the patch baseline. When executing the document to scan for patches, it records patch compliance information using the PutInventory API command. When using the document to install patches, you can run the document from a Maintenance Window and specify whether you need to reboot the target instance if required.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The compliance capability reports compliance status for instances. By default there are two types of compliance: association and patch. The association compliance detects whether a state manager association is failed on certain instances. The patch compliance, as just mentioned, checks whether patches are up to date relative to the specified patch baseline. You can also define custom compliance item (with put-compliance-items API) but the documentation isn&amp;#8217;t clear on what exactly it can achieve and where on the instance does it pull the compliance status. From the example in put-compliance-items, custom compliance type seems to check the installation of additional software package in the inventory.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Other capabilities&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Amongst the other capabilities, the one I use the most often is parameter store, which is a way to store a variable for different services to consume. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the domain of change management, the change manager is a mini change management system. Organization can use it to manage their change process such as approvals. More importantly, you can fire automation runbook from change manager and tie it back to the change control item. Change calendar allows you to block changes during specific period. Both of them are organization level capabilities.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When it comes to operations management, the Incident Manager capability allows you to create response plan for incidents. Response plan can execute runbook actions once an incident is logged. It also helps you notify the on-call incident response team. On the other hand, OpsCenter capability allows you to create OpsItem, which also includes a way to execute runbook. The OpsData can aggregate to Explorer, which is a centralized dashboard for operations data. The Explorer, OpsCenter and Incident Manager capabilities can operate at organization level. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These capabilities around change management and operations management come nowhere close to full-fledged ITSM solutions such as ServiceNow or SMAX. However, they have the ability to trigger runbooks and natively integrate with other AWS services.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is also a quick setup capability which uses pre-baked CloudFormation template to configure other services. For Patch manager the current recommendation is to use quick setup to configure patch policy.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Systems Manager has so many capabilities that I cannot cover everything in a single post. &lt;a href="https://www.youtube.com/watch?v=SYsp-AnciZA"&gt;Here&lt;/a&gt; is a good walk-through. Some capabilities like session manager, fleet manager and state manager, are extremely helpful. However, in my opinion, there are two problems with grouping all these capabilities under Systems Manager. First, With too many different capabilities, this service lacks focus, which makes it difficult to learn. Second, some capabilities have overlap with other capabilities, or another AWS services, which also makes it confusing. I try to sort out how these capabilities enable each other in the diagram below: &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="620" src="https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-2.webp" alt="" class="wp-image-12961" srcset="https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-2.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-2-300x182.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/10/ssm-diag-2-768x465.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This diagram may not be 100% accurate but it demonstrate the dependencies and can assist troubleshooting. For example, when compliance is missing data, check the execution history of run command. It also illustrates the key role of SSM agent as the underlying enabler of most of the other capabilities.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Overall, Systems Manager is extremely powerful. You can try to replace your server management solutions (e.g. Ansible, Chef and Puppet) with Systems Manager configurations. With a good understanding of its capabilities, you can build your fleet automation in an efficient and scalable way. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2023/09/orchestrate-landing-zone-with-landing-zone-accelerator-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Orchestrate Landing Zone with Landing Zone Accelerator on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/11/wordpress-security/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;WordPress Security Basics&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>AWS CDK example in Typescript – provision an AWX server</title><link>https://static.digihunch.com/2020/12/ansible-tower-lab-environment-on-aws/</link><pubDate>Sat, 19 Dec 2020 17:14:00 -0400</pubDate><guid>https://static.digihunch.com/2020/12/ansible-tower-lab-environment-on-aws/</guid><description>&lt;p class="wp-block-paragraph"&gt;This post provides an example of using AWS CDK in Typescript.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-ansible-tower-and-awx"&gt;Ansible Tower and AWX&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We have used open-source &lt;a href="https://static.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/" class="rank-math-link"&gt;Ansible&lt;/a&gt; extensively in the past. While the automation is convenient, the lack of UI makes it not as suitable as a team collaboration tool. One way to allow team collaboration with open-source Ansible, is to use Jenkins to glue the components together, as discussed in the &lt;a href="https://static.digihunch.com/2020/09/automated-deployment-pipeline-1-2/" class="rank-math-link"&gt;Automated Deployment Pipeline&lt;/a&gt; series. In this setup, the open-source Ansible remains command-line driven, with Jenkins building up the command, rather than a human user. There are many upsides in this configuration, but it is not built specifically for Ansible. Ansible is agent-less, and can be run from any host. This sounds appealing and can work well in smaller server fleet. However, since it requires some configuration on the controlling host for Ansible to function properly, it become unnecessary to configure Ansible environment on every single host (e.g. production). A typically environment only has Ansible environment configured on the bastion host. This brings the need for a dedicated controller server to drive all Ansible tasks.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.ansible.com/products/tower" class="rank-math-link"&gt;Ansible Tower&lt;/a&gt; is Red Hat&amp;#8217;s commercial enhancement to the open source Ansible, providing web-based console, REST API and other services such as Role-based Access Control (RBAC). Managing Ansible via REST API is still somewhat involving but this also enables other open-source contributions to simplify the use of API. For example &lt;a href="https://docs.ansible.com/ansible-tower/3.5.3/html/towerapi/tower_cli.html" class="rank-math-link"&gt;Tower CLI&lt;/a&gt; allows you to use Ansible Tower with simplified command. Ansible Tower has an open-source upstream project called &lt;a href="https://www.ansible.com/products/awx-project/faq" class="rank-math-link"&gt;AWX&lt;/a&gt;, maintained by Red Hat. &lt;a href="https://github.com/ansible/awx" class="rank-math-link"&gt;AWX&lt;/a&gt; is essentially a preview release of Ansible Tower without commercial support. AWX can serve as an engine for all Ansible related task. AWX server is essentially an Ansible control server. AWX, or Ansible Tower, also brings several concepts on top of Ansible:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Job template:&lt;/strong&gt; defines how an Ansible playbook should be executed, including details such as machine credential, project, inventory, and playbook file.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Job:&lt;/strong&gt; the actual execution of job template&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Project: &lt;/strong&gt;connects Ansible Tower to source control such as BitBucket. It is tied to a Git repository and a branch within that repository&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To deploy AWX on EC2 instances, there is a &lt;a href="https://aws.amazon.com/quickstart/architecture/awx/" class="rank-math-link"&gt;reference deployment&lt;/a&gt; by AWS. However, it is provided as CloudFormation template and appears to be outdated (from 2018). In our &lt;a href="https://github.com/digihunch/atlab" class="rank-math-link"&gt;project&lt;/a&gt; (late 2020, named ansible tower lab, or dubbed as &amp;#8220;atlab&amp;#8221;), we provide the infrastructure in AWS CDK (written in typescript), to provision the AWX environment. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The goal is that once the configuration is completed, you can run ansible ping against a target EC2 instance. The steps are as automated as possible. However, a number of key steps are purposefully left manual for learning purpose, such as the installation of AWX on EC2 instance.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-infrastructure-as-code"&gt;Infrastructure as Code&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In previous &lt;a href="https://static.digihunch.com/2020/12/instance-initialization-with-aws-cdk-in-python/"&gt;posting&lt;/a&gt;, I created infrastructure as code in AWS CDK with Python, so I decided to change to typescript in this project, with the assumption it is just a matter of syntax mapping. However, I underestimated the transition to a new language I never learned before. A fuzzy understanding of little details such as when to use let a=4 vs this.a=4, may produce elusive errors that takes hours to troubleshoot. I would therefore strongly recommend reading the basic syntax &lt;a class="rank-math-link" href="https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html"&gt;guide&lt;/a&gt; for typescript, before getting started. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In typescript, this example project provides an implementation of configuring autoscaling groups, including cloud init, user data, etc on AWS. Other than the language, everything else is very similar to the project in this &lt;a href="https://static.digihunch.com/2020/12/instance-initialization-with-aws-cdk-in-python/" class="rank-math-link"&gt;post&lt;/a&gt;, which was developed in Python. Also, note that the project directory structure varies slightly based on the language being used.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you are absolutely new to AWS cdk, start with this &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/hello_world.html" class="rank-math-link"&gt;app&lt;/a&gt;. It is beyond the scope of this post, to cover extensively the installation and environment configuration of AWS CDK.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the provision process for Bastion host, the cloudformation init script pulls a specific version from AWX repository, then makes slight modification. User will need to install it manually. Note that AWX can be installed on three types of platforms:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;OpenShift&lt;/li&gt;&#10;&lt;li&gt;Kubernetes&lt;/li&gt;&#10;&lt;li&gt;Docker Compose&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All are documented in their &lt;a class="rank-math-link" href="https://github.com/ansible/awx/blob/devel/INSTALL.md"&gt;README file&lt;/a&gt;. For simplicity in this project, the installation is on standalone docker compose. This is the default mode so there is no need to modify the inventory file.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-the-code-repo"&gt;The code repo&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The repository is version controlled &lt;a href="https://github.com/digihunch/atlab" class="rank-math-link"&gt;here&lt;/a&gt;. To run the project, you need to have aws cli environment, then install the required packages including node js, and npm packages such as aws cdk. Once configured, validate with command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cdk ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This should display the stacks available. Use cdk deploy to deploy each stack.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When BastionStack is deployed, dependent packages should be installed with user data and cloud init. You will just need to SSH on to the server to manually install AWX, as explained in the instruction, to manually install AWX:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-playbook -i inventory install.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then you can browse to the server (at port 80 by default). Before the log-in page for the first time, the AWX will upgrade itself, with the following screen presented:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1446" height="988" src="https://static.digihunch.com/wp-content/uploads/2020/12/image-1.png" alt="" class="wp-image-1968"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now log on with default credential (in README.md), you will have the UI for AWX:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1297" height="930" src="https://static.digihunch.com/wp-content/uploads/2020/12/image-4.png" alt="" class="wp-image-1979"/&gt;&lt;figcaption class="wp-element-caption"&gt;AWX Web Console&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From here, you can edit inventory by adding the host. Or use the helper script (~/awxcompose-helper.sh) from bastion host to create a new inventory (named Private Instance Inventory), and populate it with the hosts in the stack. The helper script does so by querying aws resource, and isssue rest API calls to AWX. After executing the script, you can see a new inventory, and the Private instance inventory should contain all hosts in the stack:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="756" height="774" src="https://static.digihunch.com/wp-content/uploads/2020/12/image-6.png" alt="" class="wp-image-1986"/&gt;&lt;figcaption class="wp-element-caption"&gt;Automatically populated inventory&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can then run Ansible ping against the host to validate connectivity. Note that during inventory creation, the ansible_user is already set to ec2-user (by the helper script):&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="921" height="908" src="https://static.digihunch.com/wp-content/uploads/2020/12/image-7.png" alt="" class="wp-image-1987"/&gt;&lt;figcaption class="wp-element-caption"&gt;Ping result&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading" id="h-some-technical-details"&gt;Some technical details&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The initialization process on Bastion host creates an RSA key pair, stores the public key to AWS, for the upcoming private instances to uses. It keeps the private key locally in order to make outgoing SSH connection to the private instances. To ensure connectivity between AWX and private instances, there are a couple of (bash) helper scripts involved. Both reflects some technical details that I had to work through.&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;awxcompose-helper.sh&lt;/strong&gt;: the initialization process pulls AWX installation file from git repo. The installation process will build a docker-compose file in ~/.awx/awxcompose, based on a template (~/awx-*/installer/roles/local_docker/templates/docker-compose.yml.j2). When user tells AWX to connect to private instance, the connection was made out of a docker container (instead of from the OS of bastion host), we need this script to map SSH key file from host to container, by modifying the template file. Without this helper, outgoing SSH connection will fail with error (Permission denied (publickey,gssapi-keyex,gssapi-with-mic)). This script is invoked in the cloud init process without requiring manual execution.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;awxinvt-helper.sh&lt;/strong&gt;: once the private stack is up and the installation has completed, we need to add the hosts to AWX inventory. This script gets the instance ID and IP addresses of the private instances, and uses Rest API calls to create inventory and populate it with hosts. Ansible has multiple &lt;a href="https://www.ansible.com/blog/summary-of-authentication-methods-in-red-hat-ansible-tower" class="rank-math-link"&gt;ways of authentication&lt;/a&gt;. This script uses the non-stateful basic authentication with each curl command requiring credential. Ansible Rest API guide is provided &lt;a href="https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html" class="rank-math-link"&gt;here&lt;/a&gt; and be wary of the &lt;a href="https://docs.ansible.com/ansible-tower/latest/html/towerapi/conventions.html" class="rank-math-link"&gt;convention&lt;/a&gt; where URI must end with a slash. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This project is just a start of AWX on AWS CDK project using Typescript. In real life scenarios, there are some work to do to make this even more automated. For example, use cfn-hup service to monitor changes of private stack, and therefore update inventories accordingly. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/12/high-performance-computing-cluster/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;High Performance Computing&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Basic Resource Object in Kubernetes 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Ansible at scale 1 of 2</title><link>https://static.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/</link><pubDate>Sun, 17 May 2020 19:38:34 -0400</pubDate><guid>https://static.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://www.ansible.com/hubfs/pdfs/Ansible-InDepth-WhitePaper.pdf"&gt;Ansible In Depth&lt;/a&gt; white paper outlines Ansible&amp;#8217;s use cases in four categories:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Configuration management&lt;/li&gt;&lt;li&gt;Application deployment&lt;/li&gt;&lt;li&gt;Orchestration: for coordinating a multi-machine process such as interacting with load balancer and rolling cluster upgrade&lt;/li&gt;&lt;li&gt;As-needed task execution: ad-hoc tasks on large number of hosts&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At work, my original automation scheme involves several &lt;a href="https://static.digihunch.com/2019/05/automation-with-ansible-a-primer/"&gt;Ansible&lt;/a&gt; Playbooks that started off simple but have been sprawling ever since. I have to spend some time to revamp the Ansible code base, following the best practices from the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#directory-layout"&gt;official documentation&lt;/a&gt;. The goal is to:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&lt;li&gt;Reduce the number of Playbooks;&lt;/li&gt;&lt;li&gt;Improve code re-usability (by Ansible roles);&lt;/li&gt;&lt;li&gt;Increase portability across different customer environment;&lt;/li&gt;&lt;li&gt;Improve security;&lt;/li&gt;&lt;li&gt;Re-organize the directory so that more team members can contribute to different parts of it.&lt;/li&gt;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Ansible code base is used by our customer service engineers, some of whom are ingrained with the established way they have been using certain Playbooks in their daily tasks. This requires me, throughout the development initiative, to ensure a consistent interaction between them and their Playbook commands.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-inventory-and-variables"&gt;Inventory and variables&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We cannot guarantee that every custom environment are identical, but what we can do is make sure that for a new environment, the only change to make is inventory and variables. This is where we can strike a balance between portability and customization. No changes should be made to tasks, roles or Playbooks when the Ansible directory is deployed at a different customer environment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If the total number of servers to manage are under 100, all can be listed in a single inventory file, and specify the only inventory file as default so the -i switch is not required for every Ansible command run. If there are more than 100 servers, it is advisable to separate them out into several inventory files in YAML, each less than 200 lines. You will have to specify inventory file with -i each time you run Ansible command.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The inventory may contain a hierarchy of groups, in order to facilitate command calls to specific groups of servers. For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;all:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; children:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; children:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_app:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; children:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_app_dc1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost01:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost03:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost05:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost07:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost09:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost11:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost13:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_app_dc2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost02:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost04:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost06:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost08:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost10:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost12:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zk_id: 3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apphost14:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_app: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_nginx: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_db: no&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_db:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; children:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_db_dc1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost01:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost03:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost05:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost07:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost09:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost11:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; clustered: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bk_node: dbhost11&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prod_db_dc2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost02:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost04:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost06:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost08:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost10:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dbhost11:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; clustered: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bk_node: dbhost12&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_app: no&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_nginx: no&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_db: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_become_pass: &amp;#39;{{site_prod_root_pw}}&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; test:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; children:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; test_dc1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tapphost01:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; test_dc2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tapphost02:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_become_pass: &amp;#39;{{site_test_root_pw}}&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_app: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_nginx: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; has_db: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vars:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_become: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_become_method su&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_become_user: root&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;User of Ansible Playbook can use -l to specify a pattern that matches a single or multiple groups, such as prod_db_dc*. In the above example, the password is not stored in clear text. They should reference a variable from a separate file encrypted by ansible-vault.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-from-playbooks-to-roles"&gt;From Playbooks to roles&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As a refresher from the &lt;a href="https://www.ansible.com/hubfs/pdfs/Ansible-InDepth-WhitePaper.pdf"&gt;white paper&lt;/a&gt;, a single &amp;#8220;task&amp;#8221; in Ansible is essentially a module call with parameters. A &amp;#8220;play&amp;#8221; consists of a series of tasks (defined under &amp;#8220;&lt;em&gt;tasks&lt;/em&gt;&amp;#8221; section) all to execute on a specified host (defined under &amp;#8220;&lt;em&gt;hosts&lt;/em&gt;&amp;#8221; section). A Playbook consist of &lt;span style="text-decoration: underline;"&gt;one or several&lt;/span&gt; plays, as shown in &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html"&gt;this example&lt;/a&gt;. In reality though, a Playbook usually contains only one play. Even that one play can grow to an unmanageable length, as complexity increases over time. This is where we need to change our approach towards scalability and manageability.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Ansible community advocates the use of &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-ansible-roles-to-abstract-your-infrastructure-environment"&gt;roles&lt;/a&gt; in place of Playbooks. The concept of Ansible &amp;#8220;role&amp;#8221; seems fairly abstract and confusing at the beginning. The word &amp;#8220;role&amp;#8221; pictures a static server state, whereas our existing Playbooks are full of actions (think of shell scripts). How would one convert an action list into static states? After some thought, I came to the understanding that roles should be thought of as desired end state. Yes, the end state is static, but that&amp;#8217;s all we care about. This is essentially the whole idea of Ansible&amp;#8217;s desired state configuration: you start from the end state and leave it to modules to complete what needs to be done to reach that state. The concept of role perfectly reflects how Ansible wants you to think about solving an infrastructure problem &amp;#8211; stop thinking about what you need to do. Instead, think about what you ultimately want, start from the desired state and work backwards.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In our own setup, the best practice turned out to be: if the Playbook involves a single play with less than 5 tasks, just stick to Playbook. We don&amp;#8217;t get rid of Playbooks just for the sake of it. Otherwise, if a Playbook has grown to more than 5 tasks, we need to think about our desired state, and either implement a new role, or incorporate it into an existing role. This is the time we have to transition from the Playbook oriented thinking to the role oriented thinking. Each role directory can include a task sub-directory with main.yml that references the rest of the tasks. Each role can define its own role-related variables. If there&amp;#8217;s a lot in common between two roles, we can even have a common role with or without its main.yml.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A simplified version of our Ansible directory structure looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── deploy-app.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── deploy-db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── inventories&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── group_vars&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── all&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── all.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── vault_all.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── prod_dc1_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── prod_dc2_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── test_dc1_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── test_dc2_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── host_vars&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── site_inventory.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── roles&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── common&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── files&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── tasks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── log.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── skip_self.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── validate_path.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── db_conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── defaults&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── files&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── handlers&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── meta&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── README.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── tasks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── start_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── stop_db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── update_cluster_var.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── templates&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── myid.j2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   ├── db_properties.j2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   │   └── zookeeper_properties.j2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── vars&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── app_conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── defaults&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── files&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── handlers&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── meta&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── README.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── tasks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── bk_app_conf.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── empty_app_conf.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── push_app_conf.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── start_app.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── stop_app.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── tar_app_conf.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── untar_app_conf.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── update_cluster_var.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── templates&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── dbref_xml.j2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── vars&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── main.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── service-app.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── service-db.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Variables specific to a group of hosts or individual hosts can be included in different yml files. When the entire directory is moved to a different customer environment, our engineers will need to update the inventory and variable files. The task, roles and Playbooks should build their logics using those variables. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-vault"&gt;Vault&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Our previous implementation of Ansible Playbook stores sudo password base64 encoded and use no_log to avoid displaying values. Now we move those to encrypted variable yml file using ansible-vault. We reference the value to encrypt as regular variable:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_pass: &amp;#39;{{passtoencrypt}}&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become_method: sudo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible_become: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we run the following:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-vault create vault_all.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This prompt for a key, and once you type in the key it opens a text editor where we can store the real password. For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;passtoencrypt: MyP@ssw0rd4real!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Use the text editor to save file. The file is now saved encrypted and must be open with correct key (aka vault password). If we call Ansible Playbook with &amp;#8211;ask-vault-pass switch then the Playbook will prompt for key input, or use include_vars to include variable from vault file (&lt;a href="https://www.toptechskills.com/ansible-tutorials-courses/ansible-include-import-variables-tutorial-examples/"&gt;example&lt;/a&gt;). If we want to even skip this, we can store the key in a file and reference them from vault_password_file in ansible.cfg&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ansible Vault has more commands to edit or view the encrypted variables in the &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/vault.html"&gt;documentation&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-optimize-connection"&gt;Optimize connection&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenSSH 5.6 and later supports multiplexing where multiple SSH sessions share a TCP connection. This can be turned on so that the following SSH connections save the time of TCP handshake. This can be configured in ansible configuration file under ssh_connection. Below is an example of this option with ControlPersist=1h. So the TCP connection is torn down after 1 hour. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ssh_connection]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh_args = -C -o ControlMaster=auto -o ControlPersist=1h&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The other option we can leverage is pipelining. Ansible takes three steps to execute a task:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&lt;li&gt;build a python script based on module used&lt;/li&gt;&lt;li&gt;copy the python script to remote host&lt;/li&gt;&lt;li&gt;execute the python script on the remote host&lt;/li&gt;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If pipelining is turned on, the python script is passed in along with the SSH session, this would save a roundtrip and increase performance. Pipelining can be configured under ssh_connection in Ansible configuration file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ssh_connection]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipelining = True&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the example below we can see by pipelining we cut the number of connection in half:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# with pipelining&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ghunch@control-host ~]$ ansible remote-host -vvvv -m ping | grep EST&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# without pipelining&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ghunch@control-host ~]$ ansible remote-host -vvvv -m ping | grep EST&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;remote-host&amp;gt; ESTABLISH SSH CONNECTION FOR USER: ghunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that if we use sudo command, then we need to disable requiretty in /etc/sudoers on the remote host.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-custom-module"&gt;Custom Module&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It&amp;#8217;s fairly straightforward to build a custom module in Ansible. Just place the module file (modulename.py) in inventory directory and use it as you would with regular Ansible module. The module file needs to be completed in Python with certain return value. Before creating custom module, you should look for existing modules to avoid re-inventing the wheel. You may also need to determine whether you simply need to &lt;a href="https://docs.ansible.com/ansible/latest/modules/script_module.html"&gt;run a python script&lt;/a&gt; on target host (with Ansible&amp;#8217;s script module), or you really need an Ansible module. The former is procedural, and the latter focus on desired state. Custom module is more used in proprietary development. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/05/understanding-where-the-memory-goes-on-linux-vm/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Balloon steals memory from virtual machines&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/05/docker-swarm-brief-notes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker Compose, Docker Stack and Docker Swarm&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>