Implementing Configuration as Code via GitLab Runner and Ansible Automation Controller

The modernization of infrastructure management has shifted from manual, reactive interventions toward a disciplined, programmatic approach known as Configuration as Code (CaC). In this paradigm, the state of an environment—including its security policies, user access, and service configurations—is defined in version-controlled files. This article examines the sophisticated integration of GitLab Runner and Ansible to facilitate this transition, specifically focusing on how GitLab CI/CD pipelines can automate the configuration of an Ansible Automation Controller. By utilizing GitLab Runner as the execution engine, organizations can achieve a tight integration between their source code repositories and their infrastructure management tools, allowing for repeatable, audited, and highly reliable deployment workflows.

The Architectural Synergy of GitLab CI/CD and Ansible

The relationship between GitLab Runner and Ansible is not merely one of sequential execution but of deep structural integration. GitLab Runner serves as the specialized application that interfaces with GitLab CI/CD to execute specific jobs within a defined pipeline. When integrated with Ansible, the Runner acts as the ephemeral compute resource that pulls automation playbooks from a repository and executes them against target endpoints, such as an Ansible Automation Controller.

This architecture provides several transformative advantages over traditional methods. While older methodologies might have relied on the Ansible controller itself or webhooks to trigger configurations, the use of GitLab CI/CD introduces a layer of abstraction that enhances stability. The tight coupling with the Git repository ensures that every change to the infrastructure is recorded in the commit history, providing an immutable audit trail. Furthermore, the ability to rerun failed jobs within the GitLab interface makes the recovery process significantly more streamlined than manual troubleshooting.

Connectivity and Environmental Requirements

For this automation framework to function, specific networking and resource prerequisites must be met to ensure the GitLab Runner can successfully communicate with the intended targets.

  • Connectivity Requirements: The GitLab Runner node must maintain active connectivity to the Ansible Automation Controller's API. This is typically achieved via ports 80 or 443, depending on whether the communication is unencrypted HTTP or encrypted HTTPS.
  • Administrative Access: Deployment requires administrative privileges on both the GitLab instance and the Ansible Automation Controller to ensure that the Runner has the necessary permissions to apply configuration changes.
  • Container Registry: A centralized container registry is required to host the Ansible Execution Environment (EE) images. These images encapsulate the necessary dependencies required for the Ansible playbooks to run successfully.
  • Execution Environment (EE) Specifications: The EE image must be pre-configured with specific Ansible collections, namely ansible.controller and infra.controller_configuration. These collections are vital for the Runner to interact with the controller API.

Deployment and Registration of the GitLab Runner

The deployment of a GitLab Runner involves several technical stages, ranging from obtaining the correct binary to registering the agent with the GitLab server. This process can be adapted for various operating systems, though RHEL 8 is a common enterprise standard for such deployments.

Obtaining and Installing the Runner

The initial step is the acquisition of the GitLab Runner RPM. It is critical to ensure that the selected RPM matches the architecture of the host processor to prevent execution errors or performance degradation. In a Red Hat Enterprise Linux (RHEL) 8 environment, the installation begins once the correct RPM is secured and available on the local filesystem.

The Registration Process

Once the Runner is installed, it must be registered to a specific GitLab instance or group. This process links the local execution engine to the centralized CI/CD orchestration layer.

  1. Navigate to the GitLab UI and select CI/CD > Runners from the left-hand sidebar.
  2. Locate and select the "Register a group runner" option at the top right of the interface.
  3. Copy the provided registration token, which is essential for the command-line registration.
  4. Execute the registration command on the Runner node:
    sudo gitlab-runner register --url https://gitlab_url
  5. Respond to the interactive prompts provided by the CLI:
    • GitLab instance URL: The base URL of the GitLab server.
    • Registration token: The token copied from the UI.
    • Description: A descriptive name for the runner (e.g., Ansible EE: controller-system-ee:dev).
    • Tags: Critical labels used to route specific jobs to specific runners. For this implementation, a tag such as controller-system-ee is used to ensure the job is picked up by the runner possessing the correct Ansible EE.
    • Executor: The environment in which the job runs. For this workflow, docker is selected to isolate the job execution within a container.
    • Default Docker image: The specific Ansible EE image residing in the container registry, such as quay.chrislab.internal/aap2/controller-system-ee:dev.

Advanced Configuration Parameters

The behavior of the GitLab Runner can be fine-tuned through various variables and configuration files. When using specialized Ansible roles to manage these runners, several parameters come into play.

Parameter Description
gitlab_runner_package_name The name of the package to install. For GitLab 10.x and later, this is gitlab-runner. For versions earlier than 10.x, it is gitlab-ci-multi-runner.
gitlab_runner_wanted_version Used on macOS and Windows to specify a precise version, such as 12.4.1.
gitlab_runner_package_version Used on Linux systems to specify the desired version of the runner.
gitlab_runner_concurrent Defines the maximum number of jobs that can execute simultaneously. By default, this equals the number of processor cores.
gitlab_runner_registration_token The token used to register the runner with the GitLab server.

Transitioning from Docker to Podman for Containerized Execution

While Docker is a standard executor, many enterprise environments—especially those prioritizing security and air-gapped operations—prefer Podman. Podman offers a daemonless, more secure alternative to Docker. To transition a GitLab Runner from a Docker-based execution model to a Podman-based one, the config.toml file must be modified to point to the Podman socket.

Modifying the configuration.toml

The configuration file for the runner is located at /etc/gitlab-runner/config.toml. To enable Podman execution, the [runners.docker] section must be updated to include a host directive pointing to the Unix socket used by Podman.

  1. Open the configuration file with elevated privileges:
    sudo vim /etc/gitlab-runner/config.toml
  2. Locate the [runners.docker] section.
  3. Add or modify the host line to point to the specific Podman socket. In a user-specific Podman environment, the path might look like:
    host = "unix:///run/user/1001/podman/podman.sock"
  4. Ensure the unix:/// prefix is included so the runner correctly identifies the target as a local Unix socket.

The resulting configuration block should appear 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"

Security and Integration in Air-Gapped Environments

In highly secure enterprise settings, "air-gapped" or disconnected environments are common. These environments have no direct access to the internet, necessitating a different approach to dependency management and image retrieval.

In a disconnected environment, the GitLab Runner cannot pull images or dependencies from public registries like Docker Hub or Quay.io. Consequently, the config.toml must be meticulously configured to point to internal, private container registries. Furthermore, the "helper image" used by GitLab Runner to facilitate communication and file transfers must also be manually adjusted in the config.toml to point to a locally hosted version of the helper image.

Pipeline Security and Code Quality

To maintain the integrity of the Configuration as Code, the GitLab CI/CD pipeline incorporates several security scanning and linting layers. This ensures that any code changes are vetted before they are applied to the Ansible Automation Controller.

  • SAST IaC: The pipeline utilizes GitLab's built-in Static Application Security Testing (SAST) for Infrastructure as Code to detect vulnerabilities in Terraform and Ansible code.
  • Container Scanning: This process scans the Ansible Execution Environment image to identify security flaws and generate a Software Bill of Materials (SBOM).
  • Ansible Linter Integration: The pipeline integrates ansible-lint with GitLab's Code Quality feature. This allows developers to see linting errors directly within the GitLab Merge Request interface.

An example of a job configuration for Ansible linting within a .gitlab-ci.yml file is provided below:

yaml 🔍 ansible-lint: stage: 🚀 ansible-deploy image: ${CI_REGISTRY_IMAGE}/${EE_IMAGE_NAME}:${EE_IMAGE_TAG} needs: [] script: - ansible-lint ansible/playbook.yml -f codeclimate | python3 -m json.tool | tee gl-code-quality-report.json || true artifacts: reports: codequality: - gl-code-quality-report.json

Automated Deployment Workflow and Lifecycle

A complete automation lifecycle begins with a dedicated project in GitLab that holds the base configuration for the controller. This project manages everything from organization creation to Lightweight Directory Access Protocol (LDAP) settings and Role-Based Access Control (RBAC).

The Execution Flow

The lifecycle of a deployment following the Configuration as Code model follows a predictable sequence:

  1. Code Commit: A developer pushes Ansible playbooks or configuration changes to the Git repository.
  2. Linting and Scanning: The GitLab Runner executes the linting and security scanning jobs.
  3. Deployment: If the scans pass, the Runner executes the Ansible playbooks against the Automation Controller using the specified EE.
  4. Health Check: After deployment, the pipeline performs a health check. This job attempts to connect to the service (such as a Tomcat server) via its HTTP port to verify a successful response.
  5. Cleanup: In lab or testing environments, the final stage of the pipeline involves destroying the provisioned resources to prevent cost or resource leakage.

Analysis of the Integration Model

The integration of GitLab Runner and Ansible represents a significant leap in operational maturity for infrastructure teams. By treating the Ansible Automation Controller as a target for programmatic configuration rather than a manual management tool, organizations effectively eliminate "configuration drift," where the actual state of a system diverges from its intended design.

The shift toward using Podman as an executor highlights an increasing trend toward security-hardened, daemonless container runtimes, which align with the Zero Trust principles becoming standard in modern DevOps. Furthermore, the capability to operate this entire stack in air-gapped environments demonstrates that high-level CI/CD orchestration is not exclusive to cloud-native, internet-connected workflows but is equally applicable to highly regulated, on-premises data centers.

The primary challenge in this model is the increased complexity of the initial setup. Managing the container registry, ensuring the correct Ansible collections are present in the EE, and maintaining the registry-to-runner connectivity requires a higher level of expertise than traditional scripting. However, the dividends paid in terms of auditability, scalability, and the ability to perform automated health checks and cleanups far outweigh the initial configuration overhead. This approach transforms infrastructure from a static entity into a dynamic, versioned, and fully automated component of the software development lifecycle.

Sources

  1. Red Hat - GitLab Runner Ansible Config Code
  2. GitHub - ansible-gitlab-runner
  3. GitLab Blog - Using Ansible and GitLab as Infrastructure for Code

Related Posts