Orchestrating Ansible Playbooks with OCI Fleet Application Management

Introduction

Oracle Cloud Infrastructure (OCI) provides a comprehensive cloud platform for customers to manage their infrastructure and applications. To further enhance the management capabilities, Fleet Application Management (FAM) in OCI orchestrates operational tasks at scale. As part of this orchestration, FAM allows users to orchestrate the Ansible, a popular open-source automation tool, to enable customers to automate and manage their OCI resources more efficiently.

By allowing users to orchestrate the Ansible with FAM, customers can use Ansible’s automation capabilities to manage their OCI infrastructures and application stacks.

In this tutorial, we are going to show how to execute the Ansible playbook through the self-hosted instance with OCI Fleet Application Management (FAM). 

Note: We are describing the process using the OCI Console, but you can also use the Oracle Cloud Infrastructure Command Line Interface (OCI CLI) or API. For information about OCI Fleet Application Management, see Fleet Application Management.

Get started - Execution of an Ansible Runbook

OCI FAM uses the runbook to trigger a script on a self-hosted instance, where an ansible playbook is executed against target compute instance. Ansible can be integrated with OCI’s native security mechanisms, such as the OCI Vault and Instance Principals, to securely manage secrets (like SSH private keys) and automate tasks across cloud instances.

Task 1: Self-hosted Instance

Create compute instance

First, launch a compute instance. Choose whether to create a new instance or select an existing one, then follow the detailed steps below.

Note: For detailed, step-by-step instructions on how to create a compute instance in Oracle Cloud Infrastructure, please refer to the official Oracle documentation: Creating an Instance

Register Self-hosted instance in OCI Fleet Application Management

A self-hosted instance can act as a central hub to monitor and manage several resources. By choosing an instance as a self-hosted instance, you can perform various checks to ensure that your Compute instances are running as expected.

  1. Log in to the OCI Console, navigate to Observability & Management, and select Fleet Application Management.
  2. Click Administration.
  3. Under Metadata management, click Self-hosted instances and Create self-hosted instance.
  4. Enter the following information for the self-hosted instances.
    • Name: Enter the self-hosted instance, avoiding any confidential information such as keys, and so on.
    • Compartment: Choose the compartment which contains the self-hosted instance
    • Select Instance →   Compartment
    • Instance: Choose the instance** in the selected compartment

Create self-hosted instance

Important : Ensure you enable the Fleet Application Management plugin on your self-hosted instance and its associated instance. This step ensures that Fleet Application Management can effectively consider these instances for lifecycle operations.

Task 2: Configure Self-hosted instance

Next, prepare your self-hosted control node by installing and validating below components: 

The Ansible collection RPM installs the OCI Ansible collection and its required dependencies. After installing the RPM, you must configure the SDK and CLI configuration file as explained in Configuring Authentication.

Installing the OCI SDK for Python

Installing the OCI SDK on a compute instance (self-hosted instance) typically involves using a package manager or a direct download and installation method, depending on the SDK language and the operating system of the compute instance(self-hosted).

  1. Download and install the SDK for Python by following instructions in the topic, SDK for Python. For additional guidance, see Downloading and Installing the SDK.
  2. After installing the SDK for Python, you must configure it using instructions in the topic Configuring the SDK.
\\# Create and activate a Python virtual environment (for pip/CLI tools)

python3 -m venv fam\\_ansible

source fam\\_ansible/bin/activate

\\# Upgrade pip inside the venv

pip install --upgrade pip

\\# Verify OCI CLI if already present in your path

oci -version

3\\.66.1

\\# If OCI CLI not installed

\$ pip install oci-cli

Collecting oci-cli`

 Downloading oci\\_cli-3.66.1-py3-none-any.whl 


Successfully installed oci-2.160.1 oci-cli-3.66.1

Installing and Configuring Ansible

Note: This installation uses Python version 3.6 and Ansible version 2.9 or later.

Installing the OCI Ansible Collection

Install the OCI Ansible collection from Ansible Galaxy by using the following command:

\\# Install the Oracle OCI Ansible Collection

ansible-galaxy collection install oracle.oci

Starting galaxy collection install process

Process install dependency map

Starting collection install process

Downloading https://galaxy.ansible.com/oracle-oci-5.5.0.tar.gz 

Installing 'oracle.oci:5.5.0' to '/root/.ansible/collections/ansible\\_collections/oracle/oci'

oracle.oci:5.5.0 was installed successfully

\\# 

ansible-doc -l | grep oracle.oci

oracle.oci (collection)  5.5.0

\\# 

ansible -m ping localhost -c local

localhost | SUCCESS => {

    "changed": false,

    "ping": "pong"

}

\\# 

ansible-doc -l | grep oci\\_ | tail

oracle.oci.oci\\_waf\\_web\\_app\\_firewall\\_policy\\_facts              

oracle.oci.oci\\_work\\_requests\\_work\\_request\\_error\\_facts         

oracle.oci.oci\\_work\\_requests\\_work\\_request\\_facts               

oracle.oci.oci\\_work\\_requests\\_work\\_request\\_log\\_entry\\_facts

If you’ve already installed the collection, you can update its modules to the latest version by adding the –force flag to the command. For example:

\$ ansible-galaxy collection install --force oracle.oci

If you need to install ansible core in the compute:

\\# Refresh repo metadata and install Ansible core + Git

sudo dnf makecache

sudo dnf install -y ansible-core git

\\# Verify Ansible

ansible --version

ansible [core 2.14.18]

config file = /etc/ansible/ansible.cfg

ansible python module location = /usr/lib/python3.9/site-packages/ansible

executable location = /usr/bin/ansible

python version = 3.9.21

Note: For detailed, step-by-step instructions on how to get started using Ansible in Oracle Cloud Infrastructure, please refer to the official Oracle documentation: Ansible Collection getting started.

Task 3: Configuring Instance Principal to access the Secret (IAM)

In this section, you’ll configure the required IAM permissions for:

Secret Access using Instance Principal

Use Instance Principals to let your compute instance call OCI services without storing or rotating user credentials. By placing the instance in a dynamic group and granting the appropriate IAM policy, the instance can securely read secrets from OCI Vault. To use instance principals, you need to create a dynamic group and policy as given below.

Create Dynamic Group

Dynamic Groups use rules to define which instances are members of the group. The matching rule can be a simple rule where you provide the OCID of the compute instance that is used for CLI. There is a Rule Builder that you can use from the OCI console to make the rule. To create a dynamic group using OCI console, see the documentation here

To create a dynamic group, do the following:

  1. Log into the OCI Console
  2. Select the hamburger menu on the top left corner, select Identity, select Dynamic Groups, and Click Create Dynamic Group.
  3. Enter its name, and a friendly description
  4. Enter a matching rule, so your instance is qualified for the dynamic group

If you are not sure how to define the rule you can use the Rule Builder to help you build the rule. Click the Rule Builder button. Enter the criteria for your rule. In this tutorial, we are using a very simple rule, where we specify the OCID of the compute instance where we will run the Ansible playbook.

Enter the matching rule and hit Add Rule button. In the Create Dynamic Group screen click Create button.

Create Policy

Once you have created the dynamic group, you need to create a policy that gives your group the privilege to read the secrets in the vault compartment. The documentation for writing policies is given here

To create a policy do the following:

  1. Log into the OCI Console
  2. Select the hamburger menu on the top left corner, select Identity, select Policies, and click Create Policy.
    • Name: Enter a name
    • Description: Add a description 
    • Compartment: Choose the compartment
    • Policy Statements: Add the following:

      allow dynamic-group ansible\_control\_node\_dg to use secret-family in compartment <vault-compartment>

      allow dynamic-group ansible\_control\_node\_dg to read vaults in compartment <vault-compartment>

    • Click Create.

      Enabling Instance Principal Authorization for OCI Secret

      Once you have created the Dynamic Group, the Matching Rule, and the Policy you are ready to use Instance Principals for authorizing OCI CLI calls for OCI Vault/secret. To enable Instance Principal authorization from the CLI, you can use the authorization option (–auth) for a command. For example:

\\# From the self-host, check a simple IAM call via Instance Principals

oci iam region list --auth instance\\_principal

\\# Test a secrets call (replace with your secret OCID)

oci secrets secret-bundle get \\

--secret-id ocid1.vaultsecret.oc1..example \\

--auth instance\\_principal \\

--raw-output \\

--query 'data."secret-bundle-content".content' | base64 -d

References

Task 4: Create Runbook for executing Ansible playbook

After installation and configuration is complete, you can write a sample playbook that uses Ansible. Following is an example playbook (named run_uptime_date_commands.yml) that execute the simple command on the remote compute instance..

Create an Ansible Playbook

\\---

\\# Play 1: Fetch the OCI Vault secret (private key) and write to a local file

\\- name: Fetch private key from OCI Vault and save to file

hosts: localhost

connection: local

gather\\_facts: false

vars:

# Override at runtime: -e secret\\_ocid= -e output\\_path= -e target\\_host= -e ssh\\_user=

secret\\_ocid: "ocid1.vaultsecret.oc1.iad.amXXXXXXaaaal4XXXXXXXXXXXXXXXXXXXXX2d7swXXXXXzq"

output\\_path: "/secret\\_value.txt"

# SSH connection parameters to the target

target\\_host: "famosXXXXXXXXXXXXXXXXX.com"

ssh\\_user: "XXXXX"

ssh\\_port: 22

# Optional: relax host key checking for first-time connections

# For production, set to "" and manage known\\_hosts strictly

ssh\\_common\\_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"

tasks:

- name: Fetch secret (base64) via OCI CLI using instance principals

ansible.builtin.command:

   argv:

         - oci

         - secrets

         - secret-bundle

         - get

         - --secret-id

         - ""

         - --auth

         - instance\\_principal

         - --raw-output

         - --query

         - "data.\\"secret-bundle-content\\".content"

      register: secret\\_b64

      changed\\_when: false

      environment:

        NO\\_PROXY: "169.254.0.0/16,127.0.0.1,localhost"

        no\\_proxy: "169.254.0.0/16,127.0.0.1,localhost"

    - name: Fail if fetch failed

      ansible.builtin.fail:

        msg: "Failed to fetch secret. stderr="

      when: secret\\_b64.rc != 0

    - name: Decode base64 to raw secret string

      ansible.builtin.set\\_fact:

        secret\\_raw: ""

    # If the secret was stored with literal "\\n", convert to real newlines (typical for PEM pasted as single line)

    - name: Normalize newlines if needed

      ansible.builtin.set\\_fact:

        secret\\_value: ""

      when: "'\\\\n' in secret\\_raw or '\\\\r\\\\n' in secret\\_raw"

    - name: Keep decoded secret as-is

      ansible.builtin.set\\_fact:

        secret\\_value: ""

      when: "'\\\\n' not in secret\\_raw and '\\\\r\\\\n' not in secret\\_raw"

    - name: Write key to file with strict permissions (0600)

      ansible.builtin.copy:

        dest: ""

        content: ""

        mode: "0600"

      no\\_log: true

    - name: Add dynamic host with SSH parameters

      ansible.builtin.add\\_host:

        name: ""

        groups: dynamic\\_targets

        ansible\\_user: ""

        ansible\\_port: ""

        ansible\\_ssh\\_private\\_key\\_file: ""

        ansible\\_ssh\\_common\\_args: ""

\\# Play 2: Connect to the target and run uptime and date

\\- name: Run health checks on target

  hosts: dynamic\\_targets

  gather\\_facts: false

  tasks:

    - name: Wait for SSH to be ready

      ansible.builtin.wait\\_for\\_connection:

        timeout: 60

        sleep: 3

    - name: Run uptime

      ansible.builtin.command: uptime

      register: uptime\\_out

      changed\\_when: false

    - name: Show uptime output

      ansible.builtin.debug:

        var: uptime\\_out.stdout

    - name: Run date

      ansible.builtin.command: date

      register: date\\_out

      changed\\_when: false

    - name: Show date output

      ansible.builtin.debug:

        var: date\\_out.stdout

Note: We recommend to keep the playbook self-contained and pass all inputs via -e so the same FAM Runbook can run different hosts/users/commands without file edits. Also we strongly recommend to store the ssh key in the vault and fetch the secret whenever required.

Create a Lifecycle Operation for Ansible Playbook Execution

Create a custom lifecycle operation in FAM for executing the DbNode OS patching script, as it does not fall under standard lifecycle categories.

  1. In the OCI Console, navigate to Fleet Application Management > Metadata Management > Lifecycle Operations
  2. Create a new lifecycle operation named, e.g., Ansible_playbook_execution_operation.
  3. Specify that it uses a self-hosted instance (configured in later steps)

    Create a Runbook through the OCI console

    This tutorial of executing the ansible playbook with the self-hosted instance uses the OCI FAM runbook, which can define how to execute the playbook with variables. For example, in order to execute the ansible playbook, you write the command to execute the ansible playbook.

To create a runbook, follow the steps:

  1. Go to the OCI Console, navigate to Fleet Application Management, and click Actions and Controls.
  2. Click Runbooks and Create runbook.
  3. Enter NameDescriptionLifecycle operationRunbook type, and select Operating System (OS) type and Estimated completion time.
  4. Create your runbook by using the visual designer, upload a YAML or JSON file, or reference your existing automation script through Bash or Python script tasks within the runbook.
    • . Upload file (playbook yml)
  5. Provide the command to execute the ansible playbook using with Bash or Python script tasks in the runbook.
  6. After adding the command to execute, you have to change properties to Self-hosted instance and choose the self-hosted instance which you have created earlier task 1.
  7. Add more tasks to customize your runbook as needed.

    Task 5: Execution Ansible Playbook

After the runbook creation, you are ready to execute the Ansible Playbook in OCI Fleet Application Management. Now you can execute the ansible playbook through the self-hosted instance. To run the runbook, create a fleet that contains the self-hosted instance.

Create a Fleet for the instance

Create a fleet in FAM to manage the self-hosted instance. No products need to be added for CR execution.

  1. In the OCI Console, go to Fleet Application Management > Fleets.
  2. Create a new fleet (e.g., ansible_playbook_fleet) and add the self-hosted instance as a resource.
  3. Ensure the fleet is in the desired compartment and set to Production environment type if applicable.

    Executing the Ansible Playbook Runbook

Trigger the runbook execution for the Ansible Playbook.

  1. In the OCI Console, go to Fleet Application Management > Fleets > ansible_playbook_fleet.
  2. Create a new job or execution, selecting the ansible_playbook_runbook and the lifecycle operation.
  3. Schedule the execution or run it immediately.
  4. Click to Execute.
  5. Executions

    Execution Output

After the execution is complete or during the execution, you can monitor the progress of ansible playbook execution.

Check the execution logs in FAM to verify the script ran successfully.

  1. In the OCI Console, go to Fleet Application Management > Lifecycle Management > Jobs.
  2. Select the execution job and view its logs to check for errors or success messages (e.g., OS patching job progress).
  3. Ansible log
Parsing the config file
Validating config format
Successfully validated config format
Processing target section
Target section not found or empty, skipping target parsing
Processing subjects section
Subject section patches is missing or invalid. Skipping
Subjects section parsed and written to file
Either Patches section not found or it has empty list
Processing script section
Script section extracted
Processing input\\_variables section
Input variables section not found or empty
Processing credentials section
Credentials section not found or has wrong format
Finished parseJSON function
Mount point: /
/var/lib/oracle-cloud-agent/plugins/oci-fams/executions/run/ocid1.famsschedulerjob.oc1.iad..amaaaaaal4gwqoaahh3nhaske7upkuttqsqkwjbesec525zxsosjxcw2nska/1861f58f-bb31-467e-9350-143060d360c9/run\\_uptime\\_date\\_commands.yml
object content written to file: /var/lib/oracle-cloud-agent/plugins/oci-fams/executions/run/ocid1.famsschedulerjob.oc1.iad..amaaaaaal4gwqoaahh3nhaske7upkuttqsqkwjbesec525zxsosjxcw2nska/1861f58f-bb31-467e-9350-143060d360c9/run\\_uptime\\_date\\_commands.yml
No input variable object storage namespace provided
Executing command: /bin/sh -c set -e; source /root/fams\\_ansible/fam\\_ansi/bin/activate; ansible-playbook -i "localhost," -c local run\\_uptime\\_date\\_commands.yml -e target\\_host="famoscompute01.xxxx.xxxx.oraclevcn.com" -e ssh\\_user="rduser" -e output\\_path="/root/fams\\_ansible/secret\\_value.txt"  -e ssh\\_port=22

PLAY [Fetch private key from OCI Vault and save to file] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*

TASK [Fetch secret (base64) via OCI CLI using instance principals] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [localhost]

TASK [Fail if fetch failed] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
skipping: [localhost]

TASK [Decode base64 to raw secret string] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [localhost]

TASK [Normalize newlines if needed] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [localhost]

TASK [Keep decoded secret as-is] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
skipping: [localhost]

TASK [Write key to file with strict permissions (0600)] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [localhost]

TASK [Add dynamic host with SSH parameters] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
changed: [localhost]

PLAY [Run health checks on target] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*

TASK [Wait for SSH to be ready] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [famoscompute01.xxx.xxx.oraclevcn.com]

TASK [Run uptime] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [famoscompute01.xxx.xxx.oraclevcn.com]

TASK [Show uptime output] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [famoscompute01.xxx.xxx.oraclevcn.com] => {
 `    `"uptime\\_out.stdout": " 08:37:26 up 89 days, 18:55,  0 users,  load average: 0.11, 0.05, 0.01"
 }

TASK [Run date] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [famoscompute01.xxx.xxx.oraclevcn.com]

TASK [Show date output] \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
ok: [famoscompute01.xxx.xxx.oraclevcn.com] => {
 `    `"date\\_out.stdout": "Tue Sep 30 08:37:27 AM GMT 2025"
 }

PLAY RECAP \\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*
famoscompute01.xxx.xxx.oraclevcn.com : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 localhost                  : ok=5    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   


 ======= Stderr ========

   stderr

Acknowledgments

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.