{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidvkjhvw4rxvw3q6ezu6dwo43f2ajmvgkoq5iqdreyrx66rpidr2u",
    "uri": "at://did:plc:5y2ps7xhcqmc2d63b73ui72s/app.bsky.feed.post/3mmciepgpoc22"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreift7gvwhzdobubwbdvot7bjqj5vn2r7ok3yogksfmgbak2izghav4"
    },
    "mimeType": "image/jpeg",
    "size": 270415
  },
  "description": "Learn how to configure Ansible for your Raspberry Pi ClusterHAT cluster, create inventories, run your first playbooks, and automate node management before deploying Kubernetes.",
  "path": "/setting-up-a-raspberry-pi-cluster-with-clusterhat-part-2-ansible/",
  "publishedAt": "2026-05-20T18:00:58.000Z",
  "site": "https://blog.php-systems.com",
  "tags": [
    "Part 1"
  ],
  "textContent": "In Part 1, we built the foundation of our Raspberry Pi cluster—installing the OS, configuring networking, and setting up SSH access across all nodes. At this point, you should be able to connect to all five hosts without being prompted for passwords.\n\nNow it’s time to make life easier.\n\nIn this guide, we’ll introduce **Ansible** to automate configuration across your cluster. By the end, you’ll be able to run commands across every node simultaneously and prepare the system for Kubernetes in Part 3.\n\n* * *\n\n## Step 1: Prepare Your Ansible Workspace\n\nOn your controller machine (the one with SSH access to all nodes), create a directory for your Ansible project:\n\n\n    mkdir ~/cluster-ansible\n    cd ~/cluster-ansible\n\nThis machine should already have:\n\n  * SSH key-based access configured (from Part 1)\n  * Connectivity to all nodes (`controller`, `p1`–`p4`)\n\n\n\n* * *\n\n## Step 2: Create Your Inventory File\n\nCreate a file named `hosts`:\n\n\n    nano hosts\n\nHere’s a structured inventory that groups your cluster effectively:\n\n\n    [kube:children]\n    controllers\n    workers\n\n    [kube:vars]\n    ansible_ssh_private_key_file=/home/pi/.ssh/id_rsa\n    ansible_user=pi\n    key_file=/home/pi/.ssh/id_rsa.pub\n\n    [controllers]\n    <controller ip> # (also 172.19.181.254)\n\n    [controllers:vars]\n    ansible_ssh_private_key_file=/home/pi/.ssh/id_rsa\n    ansible_user=pi\n    key_file=/home/pi/.ssh/id_rsa.pub\n\n    [workers]\n    172.19.181.1 # p1\n    172.19.181.2 # p2\n    172.19.181.3 # p3\n    172.19.181.4 # p4\n\n    [workers:vars]\n    ansible_ssh_private_key_file=/home/pi/.ssh/id_rsa\n    ansible_user=pi\n    key_file=/home/pi/.ssh/id_rsa.pub\n    ansible_ssh_common_args='-o ProxyCommand=\"ssh pi@<controller+ip> -W %h:%p -i /home/pi/.ssh/id_rsa\"'\n    #ansible_ssh_common_args='-F /root/.ssh/config' # Alternative if ssh is set up. Change for the config file of the user.\n\n### Why This Structure?\n\nBy defining:\n\n  * `kube` (entire cluster)\n  * `controllers`\n  * `workers`\n\n\n\n…you gain flexibility to target specific node types. This becomes especially useful later when Kubernetes roles differ between control plane and worker nodes.\n\n* * *\n\n## Step 3: Your First Playbook\n\nLet’s validate connectivity and ensure all systems are up to date.\n\nCreate a file called `kubernetes-initial.yaml`:\n\n\n    ---\n\n    - name: \"Kuberenetes Playbook for configuration\"\n      hosts: kube\n      become: yes\n\n      tasks:\n\n          - name: \"Test Reachablilty\"\n            ping:\n\n          - name: Update and Upgrade Aptitude Packages\n            apt:\n              update_cache: yes\n              upgrade: yes\n              cache_valid_time: 86400 #One day\n\n\nRun the playbook:\n\n\n    ansible-playbook -i hosts kubernetes-initial.yaml\n\nIf everything is configured correctly:\n\n  * All nodes should respond to the ping task\n  * Packages will update across the cluster\n\n\n\n* * *\n\n## Step 4: Install Common Packages\n\nOnce connectivity is confirmed, it’s time to standardize your environment.\n\nExtend your playbook with a baseline package set:\n\n\n        - name: \"Install supporting packages\"\n          apt:\n            pkg:\n              - python3-apt\n              - sudo\n              - nano\n              - tcpdump\n              - traceroute\n              - net-tools\n              - python3-pip\n              - snmpd\n              - lm-sensors\n            state: present\n\nThis gives you:\n\n  * Better diagnostics (`tcpdump`, `traceroute`)\n  * Monitoring tools (`lm-sensors`, `snmpd`)\n  * Python support for future automation\n\n\n\n* * *\n\n## Going Further: Useful Ansible Plays (Before Kubernetes)\n\nBefore jumping into Kubernetes, it’s worth strengthening your cluster with a few additional automation tasks.\n\n### 1. Set Hostnames Consistently\n\nEnsure each node has the correct hostname:\n\n\n    - name: Set hostname\n      hostname:\n        name: \"{{ inventory_hostname }}\"\n\n* * *\n\n### 2. Configure Time Synchronisation\n\nAccurate time is critical for distributed systems:\n\n\n    - name: Install and enable NTP\n      apt:\n        name: chrony\n        state: present\n\n    - name: Ensure chrony is running\n      service:\n        name: chrony\n        state: started\n        enabled: yes\n\n* * *\n\n### 3. Harden SSH Configuration\n\nImprove security by disabling password authentication:\n\n\n    - name: Disable SSH password authentication\n      lineinfile:\n        path: /etc/ssh/sshd_config\n        regexp: '^#?PasswordAuthentication'\n        line: 'PasswordAuthentication no'\n      notify: restart ssh\n\n* * *\n\n### 4. Expand Filesystem Automatically\n\nUseful for fresh SD card images:\n\n\n    - name: Expand filesystem\n      command: raspi-config --expand-rootfs\n\n* * *\n\n### 5. Create a Common User Environment\n\nStandardize `.bashrc`, aliases, or environment variables across nodes.\n\n* * *\n\n### 6. Enable Monitoring Hooks\n\nPrepare for observability by installing exporters (e.g., node exporters for Prometheus later).\n\n* * *\n\n## Where You Are Now\n\nAt this point, your cluster is:\n\n  * Fully accessible via SSH\n  * Centrally managed with Ansible\n  * Consistently configured across all nodes\n\n\n\nYou’ve effectively turned five small devices into a manageable distributed system.\n\n* * *\n\n## My complete initial configuration file\n\nI added a couple of things to the file above, like making the cluster fully power up on restart.\n\n\n    ---\n\n    - name: \"Kuberenetes Playbook for configuration\"\n      hosts: kube\n      become: yes\n\n      tasks:\n\n          # If this actually powers on the cluster, there will be an issue connecting to the workers!\n          - name: power on the cluster\n            command: clusterctrl on\n            delegate_to: controller\n\n          - name: copy clusterctrl service file over\n            ansible.builtin.copy:\n              src: files/clusterctrl.service\n              dest: /etc/systemd/system/cluster-on.service\n              owner: root\n              group: root\n              mode: '0664'\n            delegate_to: controller\n\n          - name: Ensure clusterctrl is enabled\n            service:\n              name: cluster-on.service\n              enabled: yes\n            delegate_to: controller\n\n          - name: \"Test Reachablilty\"\n            ping:\n\n          - name: Update and Upgrade Aptitude Packages\n            apt:\n              update_cache: yes\n              upgrade: yes\n              cache_valid_time: 86400 #One day\n\n          - name: \"install supporting packages\"\n            apt:\n              pkg:\n                - python3-apt\n                - sudo\n                - tcpdump\n                - traceroute\n                - net-tools\n                - python3-pip\n                - snmpd\n                - lm-sensors\n                - chrony\n              state: present\n\n    # Disabled as I personally use IPs for the controller.\n    #      - name: Set hostname\n    #        hostname:\n    #          name: \"{{ inventory_hostname }}\"\n\n          - name: Ensure chrony is running\n            service:\n              name: chrony\n              state: started\n              enabled: yes\n\n          - name: Disable SSH password authentication\n            lineinfile:\n              path: /etc/ssh/sshd_config\n              regexp: '^#?PasswordAuthentication'\n              line: 'PasswordAuthentication no'\n            notify: Restart sshd\n\n    # Disabled as this is always run and not a check.\n    #     - name: Expand filesystem\n    #       command: raspi-config --expand-rootfs\n\n      handlers:\n            - name: Restart chronyd\n              service:\n                name: chronyd\n                state: restarted\n\n            - name: Restart sshd\n              service:\n                name: sshd\n                state: restarted\n\nIf you use this full configuration file, you will also need the following file for the service ( to be placed on `files/clusterctrl.service`) - This does assume you are running an os of bookworm or more recent:\n\n\n    [Unit]\n    Description=Turn on ClusterHAT nodes\n    After=network.target\n\n    [Service]\n    Type=oneshot\n    ExecStart=/usr/sbin/clusterctrl on\n    RemainAfterExit=yes\n\n    [Install]\n    WantedBy=multi-user.target\n\n* * *\n\n## What’s Next?\n\nIn **Part 3** , we’ll take the next big step: installing Kubernetes on your Raspberry Pi cluster and turning it into a fully functional container orchestration platform.\n\nThis is where your automation work really pays off.\n\nStay tuned.",
  "title": "Setting Up a Raspberry Pi Cluster with ClusterHAT (Part 2: Ansible)",
  "updatedAt": "2026-05-20T18:01:01.380Z"
}