Ansible-basierte Infrastrukturen erfordern systematische Validierung und kontrollierte Deployment-Prozesse. CI/CD-Pipelines automatisieren die Qualitätssicherung von Playbooks, Rollen und Collections durch statische Analyse, Syntax-Prüfung und funktionale Tests. Die kontinuierliche Integration verhindert fehlerhafte Konfigurationen in Produktivumgebungen und standardisiert Deployment-Workflows.
GitLab CI: Integrierte Pipeline-Funktionalität mit YAML-basierter Konfiguration und nativer Container-Unterstützung.
GitHub Actions: Workflow-Engine mit umfangreichem Marketplace für vorgefertigte Actions und matrix-basierte Teststrategien.
Jenkins: Flexible Pipeline-Orchestrierung mit umfangreichen Plugin-Ökosystem und Pipeline-as-Code über Jenkinsfile.
Alle Plattformen unterstützen parallele Job-Ausführung, Artefakt-Management und Integration mit externen Tools für Ansible-Validierung.
Ansible-Tasks werden typischerweise in mehreren Pipeline-Phasen eingesetzt:
# .gitlab-ci.yml
stages:
- validate
- test
- deploy
variables:
ANSIBLE_HOST_KEY_CHECKING: "false"
ANSIBLE_STDOUT_CALLBACK: yaml
before_script:
- apk add --no-cache python3 py3-pip openssh-client
- pip3 install ansible ansible-lint yamllint molecule[docker]
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
validate:syntax:
stage: validate
script:
- ansible-playbook --syntax-check site.yml
- ansible-lint .
- yamllint .
only:
- merge_requests
- main
test:molecule:
stage: test
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
script:
- cd roles/webserver
- molecule test
only:
- merge_requests
- main
deploy:staging:
stage: deploy
script:
- ansible-playbook -i inventories/staging site.yml --limit staging
environment:
name: staging
url: https://staging.company.com
only:
- main
deploy:production:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml --limit production --check --diff
- ansible-playbook -i inventories/production site.yml --limit production
environment:
name: production
url: https://production.company.com
when: manual
only:
- tagsStatische Code-Analyse identifiziert Qualitätsprobleme vor der Ausführung:
# Syntax-Validierung
ansible-playbook --syntax-check playbooks/*.yml
# Ansible-spezifisches Linting
ansible-lint --parseable --quiet playbooks/ roles/
# YAML-Syntax und -Stil
yamllint --format parseable .# GitHub Actions Workflow
name: Ansible Quality Gates
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
pip install ansible ansible-lint yamllint
- name: Run yamllint
run: yamllint .
- name: Run ansible-lint
run: ansible-lint
- name: Run syntax check
run: |
for playbook in playbooks/*.yml; do
ansible-playbook --syntax-check "$playbook"
done
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run ansible-lint security rules
run: |
ansible-lint --parseable --quiet \
--enable-list no-hard-coded-passwords,risky-shell-pipeJenkins Pipeline-Beispiel:
// Jenkinsfile
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Lint') {
parallel {
stage('YAML Lint') {
steps {
sh 'yamllint --format parseable . > yamllint.log || true'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'yamllint.log',
reportName: 'YAML Lint Report'
])
}
}
stage('Ansible Lint') {
steps {
sh 'ansible-lint --parseable . > ansible-lint.log || true'
archiveArtifacts artifacts: 'ansible-lint.log'
}
}
}
}
stage('Syntax Check') {
steps {
script {
def playbooks = sh(
script: "find . -name '*.yml' -path './playbooks/*'",
returnStdout: true
).trim().split('\n')
playbooks.each { playbook ->
sh "ansible-playbook --syntax-check ${playbook}"
}
}
}
}
}
}Molecule automatisiert das Testen von Ansible-Rollen durch Bereitstellung isolierter Test-Umgebungen. Das Framework unterstützt verschiedene Treiber (Docker, Vagrant, Podman) und Testframeworks (Testinfra, pytest).
Molecule-Konfiguration für eine typische Webserver-Rolle:
# molecule/default/molecule.yml
---
dependency:
name: galaxy
options:
requirements-file: requirements.yml
driver:
name: docker
platforms:
- name: instance-ubuntu
image: ubuntu:20.04
pre_build_image: true
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /lib/systemd/systemd
- name: instance-centos
image: centos:8
pre_build_image: true
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /usr/sbin/init
provisioner:
name: ansible
config_options:
defaults:
stdout_callback: yaml
verifier:
name: testinfraTestdefinition:
# molecule/default/tests/test_default.py
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']
).get_hosts('all')
def test_nginx_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
def test_nginx_running(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
def test_nginx_config_valid(host):
cmd = host.run("nginx -t")
assert cmd.rc == 0
def test_website_accessible(host):
cmd = host.run("curl -s -o /dev/null -w '%{http_code}' http://localhost")
assert "200" in cmd.stdoutPipeline-Integration mit Matrix-Testing:
# .gitlab-ci.yml - Molecule mit Matrix
test:molecule:
stage: test
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
parallel:
matrix:
- MOLECULE_SCENARIO: [default, ubuntu-18, centos-7]
script:
- cd roles/$ROLE_NAME
- molecule test --scenario-name $MOLECULE_SCENARIO
artifacts:
reports:
junit: roles/*/molecule/*/junit.xmlVault-Passwörter werden über CI-Umgebungsvariablen bereitgestellt:
# Verschlüsseltes Inventory
ansible-vault encrypt_string 'production_db_password' --name 'db_password'
# Pipeline-Nutzung
echo "$ANSIBLE_VAULT_PASSWORD" > .vault_pass
ansible-playbook -i inventories/production site.yml --vault-password-file .vault_pass
rm .vault_passGitLab CI Vault-Integration:
# .gitlab-ci.yml
deploy:production:
stage: deploy
variables:
ANSIBLE_VAULT_PASSWORD_FILE: /tmp/vault_pass
before_script:
- echo "$VAULT_PASSWORD" > /tmp/vault_pass
- chmod 600 /tmp/vault_pass
script:
- ansible-playbook -i inventories/production site.yml
after_script:
- rm -f /tmp/vault_pass
environment:
name: productionGitHub Actions mit verschlüsselten Secrets:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create vault password file
run: echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_pass
- name: Deploy with Ansible
run: |
ansible-playbook \
-i inventories/production \
--vault-password-file .vault_pass \
--extra-vars "release_version=${{ github.event.release.tag_name }}" \
site.yml
env:
ANSIBLE_HOST_KEY_CHECKING: falseJenkins Credentials Plugin:
// Jenkinsfile
pipeline {
agent any
environment {
ANSIBLE_VAULT_PASSWORD_FILE = credentials('ansible-vault-password')
}
stages {
stage('Deploy') {
steps {
ansiblePlaybook(
playbook: 'site.yml',
inventory: 'inventories/production',
vaultCredentialsId: 'ansible-vault-password',
extras: '--limit production'
)
}
}
}
}Feature-Branch-Workflow mit Ansible:
# Feature-Entwicklung
git checkout -b feature/nginx-ssl-config
# Entwicklung und Tests
git commit -m "Add SSL configuration for nginx role"
git push origin feature/nginx-ssl-config
# Merge Request/Pull Request für Review
# Release-Tagging
git checkout main
git tag -a v2.1.0 -m "Release version 2.1.0 - SSL support"
git push origin v2.1.0GitFlow-basierte Pipeline:
# .gitlab-ci.yml - GitFlow Integration
variables:
DEPLOY_STAGING: "develop"
DEPLOY_PRODUCTION: "main"
deploy:staging:
script:
- ansible-playbook -i inventories/staging site.yml
only:
- develop
deploy:production:
script:
- ansible-playbook -i inventories/production site.yml
only:
- main
- tags
hotfix:production:
script:
- ansible-playbook -i inventories/production site.yml --tags hotfix
only:
- /^hotfix\/.*$/
when: manualSemantic Versioning für Ansible-Projekte:
# galaxy.yml - Collection Versioning
namespace: company
name: infrastructure
version: "${CI_COMMIT_TAG:-0.0.0-dev}"
description: Company infrastructure automation
# .gitlab-ci.yml - Automated Versioning
build:collection:
stage: build
script:
- |
if [[ $CI_COMMIT_TAG ]]; then
export VERSION=$CI_COMMIT_TAG
else
export VERSION="0.0.0-${CI_COMMIT_SHORT_SHA}"
fi
- sed -i "s/version: .*/version: $VERSION/" galaxy.yml
- ansible-galaxy collection build
artifacts:
paths:
- "*.tar.gz"
expire_in: 1 weekEnvironment-spezifische Konfiguration:
# inventories/staging/group_vars/all.yml
environment: staging
database_host: staging-db.company.com
api_replicas: 1
debug_mode: true
# inventories/production/group_vars/all.yml
environment: production
database_host: prod-db.company.com
api_replicas: 3
debug_mode: falsePipeline mit Environment-Gates:
# .gitlab-ci.yml - Environment Progression
deploy:staging:
stage: deploy
script:
- ansible-playbook -i inventories/staging site.yml
environment:
name: staging
url: https://staging.company.com
only:
- develop
deploy:production:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml --check --diff
- read -p "Continue with deployment? (y/N): " confirm
- [[ $confirm == [yY] ]] || exit 1
- ansible-playbook -i inventories/production site.yml
environment:
name: production
url: https://production.company.com
when: manual
only:
- mainGranulare Deployment-Kontrolle:
# Playbook mit strategischen Tags
- name: Configure application servers
hosts: app_servers
tasks:
- name: Install packages
package:
name: "{{ item }}"
state: present
loop: "{{ required_packages }}"
tags: [packages, setup]
- name: Configure application
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: restart application
tags: [config, app]
- name: Deploy application code
git:
repo: "{{ app_repository }}"
dest: "{{ app_directory }}"
version: "{{ app_version | default('main') }}"
notify: restart application
tags: [deploy, app]
- name: Update database schema
command: "{{ app_directory }}/bin/migrate.py"
tags: [database, migrate]
when: run_migrations | default(false)Pipeline mit selektiver Tag-Ausführung:
# .gitlab-ci.yml - Tag-basierte Deployments
deploy:config-only:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml --tags config
when: manual
only:
- main
deploy:hotfix:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml --tags hotfix,config
when: manual
only:
- /^hotfix\/.*$/
deploy:full:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml
when: manual
only:
- tagsAutomatisierte Rollback-Mechanismen:
# Playbook mit Rollback-Funktionalität
- name: Deploy application with rollback capability
hosts: app_servers
vars:
app_releases_path: /opt/app/releases
app_current_path: /opt/app/current
keep_releases: 5
tasks:
- name: Create releases directory
file:
path: "{{ app_releases_path }}"
state: directory
tags: [deploy]
- name: Deploy new release
git:
repo: "{{ app_repository }}"
dest: "{{ app_releases_path }}/{{ ansible_date_time.epoch }}"
version: "{{ app_version }}"
register: new_release
tags: [deploy]
- name: Update symlink to new release
file:
src: "{{ new_release.dest }}"
dest: "{{ app_current_path }}"
state: link
force: yes
notify: restart application
tags: [deploy]
- name: Cleanup old releases
shell: |
cd {{ app_releases_path }} && \
ls -1t | tail -n +{{ keep_releases + 1 }} | xargs rm -rf
tags: [deploy, cleanup]
# Rollback-Playbook
- name: Rollback application
hosts: app_servers
tasks:
- name: Get previous release
shell: |
cd {{ app_releases_path }} && \
ls -1t | head -n 2 | tail -n 1
register: previous_release
tags: [rollback]
- name: Rollback to previous release
file:
src: "{{ app_releases_path }}/{{ previous_release.stdout }}"
dest: "{{ app_current_path }}"
state: link
force: yes
notify: restart application
tags: [rollback]Integration von Deployment-Monitoring:
# Pipeline mit Health Checks
deploy:production:
stage: deploy
script:
- ansible-playbook -i inventories/production site.yml
- sleep 30 # Warten auf Service-Start
- ansible-playbook -i inventories/production health-check.yml
after_script:
- |
if [ $CI_JOB_STATUS = "failed" ]; then
ansible-playbook -i inventories/production rollback.yml
curl -X POST "$SLACK_WEBHOOK" -d '{"text":"Deployment failed and rolled back!"}'
else
curl -X POST "$SLACK_WEBHOOK" -d '{"text":"Deployment successful!"}'
fi
environment:
name: productionHealth-Check Playbook:
# health-check.yml
- name: Verify deployment health
hosts: app_servers
tasks:
- name: Check application endpoint
uri:
url: "http://{{ inventory_hostname }}:8080/health"
method: GET
status_code: 200
timeout: 10
retries: 3
delay: 10
- name: Verify database connectivity
command: "{{ app_directory }}/bin/db-check.py"
register: db_check
failed_when: db_check.rc != 0
- name: Check disk space
shell: df -h / | awk 'NR==2 {print $5}' | sed 's/%//'
register: disk_usage
failed_when: disk_usage.stdout | int > 90