Architecting Configuration-as-Code via GitLab Runner and Ansible Automation

The paradigm shift toward Configuration-as-Code (CaC) has redefined how enterprise environments manage complex automation controllers. By integrating GitLab CI/CD with Ansible, organizations move away from manual, imperative configuration steps and toward a declarative, version-controlled lifecycle. This integration leverages the GitLab Runner—a lightweight, highly scalable application designed to execute jobs within a GitLab CI/CD pipeline—to orchestrate Ansible playbooks that manage the state of an Ansible Automation Controller.

In a modern DevOps ecosystem, the GitLab Runner acts as the execution engine that bridges the gap between the version-controlled repository and the target infrastructure. When paired with Ansible, the runner can trigger playbooks that interact with the Automation Controller API, applying configurations ranging from organization structures and LDAP integrations to sophisticated Role-Based Access Control (RBAC) settings. This approach ensures that every change to the automation infrastructure is audited, tested, and repeatable, reducing the risk of configuration drift and manual error.

The utility of this architecture extends beyond mere deployment. It enables a continuous integration and continuous deployment (CI/CD) workflow for infrastructure itself. By treating the controller configuration as code, teams can utilize the same rigorous testing standards applied to application software—linting playbooks, testing against staging environments, seeking human approval, and finally deploying to production—all within a single, unified platform. This eliminates the friction of managing disparate tools and centralizes the entire automation lifecycle within the GitLab environment.

The GitLab Runner and Ansible Integration Ecosystem

The GitLab Runner serves as the primary executor for GitLab CI/CD pipelines. It is an application that communicates with the GitLab server to pick up jobs and execute them in a specified environment. In the context of Ansible automation, the runner provides the isolated execution environment necessary to run playbooks without polluting the host machine's configuration.

The following table outlines the core components required to establish a functional GitLab-Ansible automation pipeline:

Component Role in the Ecosystem Technical Requirement/Specification
GitLab Instance The orchestration platform and source of truth for code and CI/CD definitions. Admin access required; hosts the .gitlab-ci.yml file.
GitLab Runner The execution agent that runs the CI/CD jobs. Requires connectivity to GitLab via ports 80 or 443.
Ansible Automation Controller The target system being managed via Configuration-as-Code. Requires admin access and API accessibility.
Ansible Execution Environment (EE) A container image containing the necessary Ansible versions and collections. Must include ansible.controller and infra.controller_configuration collections.
Container Registry The storage location for the Ansible Execution Environment images. Must be accessible by the GitLab Runner.

The integration is particularly robust when using a Container-based approach. By utilizing an Ansible Execution Environment (EE), the runner ensures that the exact versions of Ansible, Python libraries, and specific collections are available for every job. This solves the "it works on my machine" problem by providing a standardized, immutable runtime for every playbook execution.

Technical Specifications for Ansible-Based GitLab Runner Deployment

When deploying a GitLab Runner specifically optimized for Ansible workloads, several configuration variables and versioning requirements must be addressed, especially when utilizing Ansible roles for deployment.

Ansible Role Requirements and Versioning

For administrators utilizing Ansible roles to manage the installation and configuration of the runner (such as the specialized ansible-gitlab-runner role), strict dependency management is mandatory.

  • Ansible Version Requirement: The deployment process requires Ansible version 2.13 or higher to ensure compatibility with modern modules and collection structures.
  • Galaxy Collections: The deployment must include the installation of specific Ansible Galaxy collections as defined in a requirements.yml file.
  • Package Naming Conventions: In GitLab versions 10.x and later, the package name is gitlab-runner. For legacy environments using versions prior to 10.x, the variable gitlab_runner_package_name must be set to gitlab-ci-multi-runner.
  • Version Control for Runners: Administrators can specify the desired version of the runner using gitlab_runner_wanted_version for macOS and Windows environments, or gitlab_runner_package_version for Linux-based systems.
  • Concurrency Management: The gitlab_runner_concurrent variable defines the maximum number of jobs that can run simultaneously. By default, this is set to the number of available processor cores on the host.

Registration and Token Management

The process of linking a runner to a GitLab instance involves a registration phase that requires specific credentials and metadata.

  • Registration Token: This is the unique identifier used to authorize the runner. If gitlab_runner_registration_token_type is set to the deprecated registration-token, the token can be used globally. In modern implementations, a unique token must be specified for each individual runner within the gitlab_runner_runners list.
  • Group vs. Instance Runners: Users can register a group runner via the GitLab UI under CI/CD > Runners by selecting "Register a group runner" and copying the resulting token.
  • Executor Selection: During registration, the executor defines the environment where the job runs. For Ansible automation, the docker executor is preferred over shell or ssh because it allows the use of specialized Ansible Execution Environments.
  • Tagging Strategy: Tags are critical for job routing. A runner must be assigned tags (e.g., controller-system-ee) so the GitLab CI/CD engine knows to dispatch specific Ansible jobs to the runner equipped with the correct container image.

Implementing the Runner in Disconnected and Containerized Environments

A significant challenge in enterprise environments is the "air-gapped" or disconnected scenario, where security protocols prohibit direct internet access. This necessitates a different approach to dependency management and runner configuration.

The Disconnected Environment Strategy

In an air-gapped environment, the runner cannot pull images from public registries like Docker Hub or Quay.io during runtime. All dependencies, including the GitLab Runner RPM and the Ansible Execution Environment images, must be pre-staged in a local container registry.

  • RPM Acquisition: The administrator must manually obtain the correct GitLab Runner RPM corresponding to the specific processor architecture of the RHEL 8 node.
  • Image Availability: The Ansible EE image, containing the necessary ansible.controller and infra.controller_configuration collections, must be hosted on a local, reachable container registry.
  • Connectivity: While the runner is disconnected from the internet, it must still maintain port 80 or 443 connectivity to the local GitLab instance and the Automation Controller API.

Transitioning from Docker to Podman

In many modern Linux distributions, particularly RHEL, Podman is the preferred container engine over Docker. To redirect a GitLab Runner from the Docker daemon to the Podman socket, the config.toml file must be manually modified.

The configuration process involves the following steps:

  1. Access the configuration file using elevated privileges:
    sudo vim /etc/gitlab-runner/config.toml

  2. Locate the [runners.docker] section within the file.

  3. Add the host parameter to point to the Podman socket. For a specific user, the path typically follows the pattern of the Podman socket location.

The resulting configuration block should be structured as follows:

toml [runners.docker] tls_verify = false image = "aapah.chrislab.internal/controller-system-ee:latest" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0 host = "unix:///run/user/1001/podman/podman.sock"

Note that the unix:/// prefix is mandatory to instruct the runner to look for the socket on the local machine. This transition is vital for environments utilizing rootless containerization or those where Docker is not the native container runtime.

Orchestrating the Ansible Automation Controller via CI/CD

Once the runner is registered and configured to use the correct Ansible Execution Environment, the final stage is the creation of the CI/CD project. This project contains the .gitlab-ci.yml file, which acts as the blueprint for the automation lifecycle.

Pipeline-as-Code Workflow

The core benefit of this setup is the ability to manage the Automation Controller's internal state through a pipeline. This includes:

  • Organization Management: Automating the creation and lifecycle of different organizations within the controller.
  • User and Team Configuration: Managing access levels and team groupings.
  • Identity Management: Configuring LDAP integration for centralized authentication.
  • RBAC Enforcement: Deploying complex role-based access control policies.

The workflow follows a structured progression:

  1. Code Commit: An administrator updates an Ansible playbook or a configuration variable in the Git repository.
  2. Trigger: The GitLab CI/CD engine detects the change and initiates a pipeline.
  3. Job Dispatch: The engine identifies the runner tagged with the required executor/image (e.g., controller-system-ee) and dispatches the job.
  4. Execution: The GitLab Runner pulls the specified Ansible EE from the local container registry and executes the playbook.
  5. API Interaction: The Ansible playbook communicates with the Automation Controller via the API to apply the desired state.

The Importance of Execution Environments in Pipelines

The Execution Environment (EE) is the cornerstone of reliable Ansible automation in a CI/CD context. Without a properly configured EE, the runner would lack the specific collections required to interact with the controller.

The following table describes the essential components of a specialized Ansible EE for controller management:

Component Purpose Implementation Detail
ansible.controller Collection for managing Automation Controller. Must be pre-installed in the image.
infra.controller_configuration Collection for infrastructure-level settings. Must be pre-installed in the image.
Container Registry Provides the image to the runner. Must be accessible via the network.
Runner Tag Maps the job to the EE. e.g., controller-system-ee.

Conclusion

The integration of GitLab Runner and Ansible provides a sophisticated framework for managing enterprise automation infrastructure. By transitioning from manual configuration to a Configuration-as-Code model, organizations achieve a level of consistency and auditability that is impossible through traditional methods. The ability to swap between Docker and Podman, manage runners in air-gapped environments, and leverage specialized Execution Environments ensures that this architecture is adaptable to the most stringent security and operational requirements. Ultimately, this synergy between GitLab CI/CD and Ansible transforms the Automation Controller from a standalone tool into a dynamic, version-controlled component of a broader DevOps ecosystem, enabling continuous improvement and rapid scaling of automation capabilities.

Sources

  1. Red Hat: GitLab Runner Ansible Config Code
  2. GitHub: ansible-gitlab-runner role
  3. OneUptime: How to run Ansible Playbooks in GitLab CI/CD

Related Posts