The Network File System (NFS) has remained a cornerstone of Linux infrastructure since its inception in the 1980s, providing a standardized mechanism for distributed file sharing across heterogeneous server environments. While the fundamental concepts of NFS are simple—allowing a client to access files over a network as if they were on a local disk—the operational challenge lies in the consistency of deployment. Manually configuring a single NFS server is a trivial task; however, ensuring that twenty, fifty, or one hundred servers maintain identical export configurations, permission sets, and security postures is an administrative nightmare. This is where Ansible transforms the process from a tedious, manual exercise into a repeatable, idempotent software-defined infrastructure pattern. By leveraging Ansible, engineers can move away from fragile manual configurations toward a declarative state where the desired state of the filesystem is defined in code and enforced across the entire fleet.
The Structural Foundation of NFS Infrastructure
A production-ready NFS deployment is characterized by a hub-and-spoke architecture. At the center are one or more NFS servers that export specific directories (the "exports"), and surrounding these are numerous clients that mount these directories for application data, shared configurations, or centralized logging.
To manage this at scale, a structured Ansible inventory is required. The inventory must differentiate between the role of the provider and the consumer to ensure that server-side packages and client-side mount points are not cross-applied.
```ini
inventory/nfs
[nfsservers]
nfs-server-01 ansiblehost=10.0.1.10
[nfsclients]
app-01 ansiblehost=10.0.2.10
app-02 ansiblehost=10.0.2.11
app-03 ansiblehost=10.0.2.12
web-01 ansiblehost=10.0.3.10
web-02 ansiblehost=10.0.3.11
[nfsclients:vars]
nfsserver_ip=10.0.1.10
```
The technical necessity of this separation is rooted in the different software dependencies required for each role. The server requires the kernel-level NFS daemon and export management tools, while the client primarily requires the mount utilities and network-aware filesystem drivers. By defining nfs_server_ip as a variable within the nfs_clients group, Ansible can dynamically inject the server's address into the client's /etc/fstab or mount commands, ensuring that any change to the server IP only needs to be updated in one place in the inventory.
Comprehensive NFS Server Deployment on Red Hat Systems
Implementing an NFS server on Red Hat-like systems requires a precise sequence of operations to ensure the service is available, secure, and correctly exporting data. The process involves six critical stages of automation.
Package Installation and Directory Preparation
The first step is the installation of the nfs-utils package. This package provides the essential tools for managing NFS shares, including the exportfs utility and the necessary kernel modules. Using the ansible.builtin.yum module (or dnf in newer versions), the administrator ensures the software is present.
Once the software is installed, the actual directory to be shared must be created. This is handled by the ansible.builtin.file module. The technical requirement here is not just the creation of the directory, but the explicit assignment of ownership and permissions. If a directory is created as root:root with 755 permissions, but the intended NFS users are non-privileged, the mount will succeed, but the users will encounter "Permission Denied" errors.
Export Configuration and the /etc/exports File
The core of NFS configuration resides in /etc/exports. This file defines which directories are shared, who can access them, and what permissions they have (e.g., rw for read-write or ro for read-only).
There are two primary methods for managing this file via Ansible:
- The
ansible.builtin.lineinfileapproach: This is used for simple, one-off additions of shares. It is an effective way to append a specific export line without overwriting the entire file. - The
ansible.builtin.templateapproach: For complex environments, using a Jinja2 template is superior. It allows for a loop over a list of exports, ensuring the file is exactly as defined in the code.
Example of a template-based export configuration:
yaml
- name: Configure NFS exports
ansible.builtin.template:
src: exports.j2
dest: /etc/exports
owner: root
group: root
mode: '0644'
backup: true
notify: reload nfs exports
The corresponding exports.j2 template allows for dynamic generation based on a list of export objects:
```jinja2
/etc/exports - Managed by Ansible
Do not edit manually, changes will be overwritten
{% for export in nfs_exports %}
{{ export.path }} {{ export.clients }}({{ export.options }})
{% endfor %}
```
Service Activation and Daemon Management
The NFS server does not operate in isolation; it relies on several supporting services. The rpcbind service is critical as it maps the RPC program numbers to universal addresses. Without rpcbind, the NFS server cannot communicate with clients using the remote procedure call mechanism.
The following systemd configurations are mandatory:
rpcbind: Must be started and enabled to ensure the server can map NFS requests.nfs-server: The primary daemon responsible for managing the exports.
```yaml
- name: Enable rpcbind
ansible.builtin.systemd:
name: rpcbind
state: started
enabled: true
- name: Enable NFS server
ansible.builtin.systemd:
name: nfs-server
state: started
enabled: true
```
Applying Export Changes
A common mistake in NFS automation is modifying /etc/exports but failing to notify the kernel of the changes. The exportfs command is the only way to refresh the export table without restarting the entire NFS service, which would disconnect all current clients.
Since there is no dedicated Ansible module for exportfs, the ansible.builtin.command module is used. The command exportfs -ra is executed to re-export all directories listed in /etc/exports and synchronize them with the kernel's internal table.
yaml
- name: reload nfs exports
ansible.builtin.command:
cmd: exportfs -ra
Firewall Configuration for NFS Traffic
NFS is a network-intensive service that requires specific ports to be open. On Red Hat systems, firewalld is the standard manager. Failing to open these ports results in "Connection Timed Out" errors on the client side.
The following services must be explicitly enabled in the firewall:
nfs: The primary port for NFS traffic (typically TCP 2049).rpc-bind: Used for the portmapper service (TCP/UDP 111).mountd: Used for the mount daemon.
yaml
- name: Open NFS firewall ports
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- nfs
- rpc-bind
- mountd
when: ansible_os_family == "RedHat"
failed_when: false
Advanced Configuration: Security, Versions, and Kerberos
Modern production environments require more than basic connectivity; they require security and protocol optimization.
NFS Version Control
To prevent the use of legacy, insecure versions of NFS, the server can be configured to only allow newer protocols. This is managed via /etc/nfs.conf.d/nfsv4.conf. By disabling version 2 and 3 and enabling version 4, 4.1, and 4.2, the administrator enforces a more secure and efficient communication standard.
yaml
- name: Configure NFS version settings
ansible.builtin.copy:
dest: /etc/nfs.conf.d/nfsv4.conf
mode: '0644'
content: |
[nfsd]
vers2=n
vers3=n
vers4=y
vers4.1=y
vers4.2=y
notify: restart nfs-server
Kerberos Integration and Secure Exports
For environments requiring high security, Kerberos is used to provide strong authentication. This involves the gssproxy service, which manages the GSS-API (Generic Security Services Application Program Interface) for the NFS server.
To implement a Kerberos-secured export, the /etc/exports file must specify the sec=krb5p option, which ensures that all traffic is encrypted (privacy).
```yaml
- name: Configure Kerberos-secured exports
ansible.builtin.lineinfile:
path: /etc/exports
line: "/srv/nfs/secure *.{{ nfsdomain }}(rw,sync,sec=krb5p,nosubtree_check)"
create: true
mode: '0644'
notify: reload nfs exports
- name: Enable gssproxy for Kerberos support
ansible.builtin.systemd:
name: gssproxy
state: started
enabled: true
```
NFS Client Configuration and Mounting Strategies
The client-side configuration ensures that the shared storage is mounted correctly and persists across reboots.
Installation and Mount Point Creation
The client must first install the NFS utilities. Following this, the mount points—directories where the remote share will be attached—must be created using the ansible.builtin.file module.
Static Mounts and fstab Integration
Static mounts are configured by adding entries to /etc/fstab. The choice of mount options is critical for system stability:
rworro: Read-write or read-only access.hard: The client will retry indefinitely if the server goes down, preventing data corruption.intr: Allows the process to be interrupted if the NFS server is unreachable.timeo=600: Sets the timeout to 60 seconds.retrans=2: Limits the number of retries before an error is reported._netdev: Tells the system to wait for network connectivity before attempting to mount.
Example of a client configuration playbook:
yaml
- name: Set up NFS clients
hosts: nfs_clients
become: true
vars:
nfs_mounts:
- src: "{{ nfs_server_ip }}: /srv/nfs/shared"
path: /mnt/nfs/shared
opts: "rw,hard,intr,timeo=600,retrans=2,_netdev"
- src: "{{ nfs_server_ip }}: /srv/nfs/web_content"
path: /mnt/nfs/web_content
opts: "ro,hard,intr,timeo=600,retrans=2,_netdev"
tasks:
- name: Install NFS client
ansible.builtin.yum:
name: nfs-utils
state: present
Automating NFS with Identity Management (IdM) and Autofs
In enterprise environments using Red Hat Identity Management (IdM), manually managing mount points for thousands of users is impractical. This is solved using the autofs service and IdM automount maps.
The Role of Automount (Autofs)
The autofs service provides a dynamic mounting mechanism. Instead of mounting a directory at boot (which can slow down boot times if the server is unreachable), autofs mounts the directory only when a user attempts to access it. Once the directory is no longer in use, the system automatically unmounts it.
Within an IdM domain, this is further centralized. The automount maps are stored in the LDAP directory of the IdM server. This allows an administrator to define a "location" (a container for map entries) and a "map" that associates a remote NFS share with a local mount point.
Implementing IdM Automounts with Ansible
To automate this, the ansible-freeipa package must be installed on the control node. The process involves managing the automount location and the map and key present on the IdM server.
Technical requirements for IdM automounting:
- Control Node: Ansible version 2.14 or later.
- Package: ansible-freeipa must be installed.
- Authentication: An ipaadmin_password stored in an Ansible vault.
- Node status: The target node must already be an IdM client, server, or replica.
In the scenario where a server nfs-server.idm.example.com exports /exports/project, the autofs configuration allows any user in the developers group to access this as /devel/project/ on any IdM client within the raleigh automount location.
The deployment workflow involves copying the automount-location-present.yml playbook from the system documentation directory (/usr/share/doc/ansible-freeipa/playbooks/automount/) and customizing the map and key to match the specific FQDN of the NFS server and the desired mount path.
Monitoring and Health Verification of NFS Deployments
Once the NFS infrastructure is deployed, ongoing monitoring is required to ensure connectivity and performance.
Verifying Active Exports
To verify that the server is correctly exporting the intended directories, the exportfs -v command can be used. This provides a detailed list of the exports and their current status.
```yaml
- name: Show active exports
ansible.builtin.command:
cmd: exportfs -v
register: activeexports
changedwhen: false
- name: Display active exports
ansible.builtin.debug:
var: activeexports.stdoutlines
```
Monitoring Client Connectivity
To identify which clients are currently connected to the NFS server, the ss (socket statistics) command is used to filter for established connections on port 2049.
```yaml
- name: Show connected NFS clients
ansible.builtin.command:
cmd: ss -tn state established '( sport = :2049 )'
register: nfsclients
changedwhen: false
- name: Display connected clients
ansible.builtin.debug:
var: nfsclients.stdoutlines
```
Comparison of Deployment Methods
The following table compares the different methods of NFS configuration discussed in this guide.
| Method | Use Case | Ansible Modules | Persistence | Management Overhead |
|---|---|---|---|---|
| Standard Export | Basic file sharing between servers | yum, file, template, command |
Static via /etc/exports |
Low |
| Kerberized NFS | High-security enterprise environments | lineinfile, systemd (gssproxy) |
Static via /etc/exports |
Medium |
| IdM Automount | Large-scale user environments | ansible-freeipa, autofs |
Dynamic via LDAP | High (Initial) / Low (Ongoing) |
Conclusion
The automation of Network File Systems through Ansible represents a transition from manual system administration to Infrastructure as Code (IaC). By meticulously orchestrating the installation of nfs-utils, the configuration of /etc/exports via Jinja2 templates, and the precise management of systemd services and firewalld rules, administrators can eliminate the configuration drift that typically plagues large-scale storage deployments.
The integration of autofs and Red Hat Identity Management further enhances this by moving mount configurations from local files to a centralized LDAP directory, allowing for a seamless user experience where shares are mounted on-demand based on group membership and location. Furthermore, the implementation of NFSv4.2 and Kerberos encryption transforms a legacy protocol into a secure, modern transport mechanism. The ability to monitor these systems using Ansible's debug and command modules ensures that the infrastructure is not only deployed correctly but remains healthy and performant throughout its lifecycle.