Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Ngày xuất bản
Ngày xuất bản

Ansible Playbooks Phần 4: Ví dụ thực tế và Best Practices

Phần 4: Ví dụ thực tế và Best Practices:

  • Deploy LEMP stack hoàn chỉnh
  • Project structure organization
  • Ansible Vault cho security
  • Idempotency best practices
  • Error handling patterns
  • Testing strategies
  • Performance optimization
  • Documentation practices

1️⃣ Ví dụ: Deploy LEMP Stack

---
- name: Deploy LEMP Stack (Linux, Nginx, MySQL, PHP)
  hosts: web
  become: yes

  vars:
    mysql_root_password: "{{ vault_mysql_root_password }}"
    db_name: myapp_db
    db_user: myapp_user
    db_password: "{{ vault_db_password }}"
    php_version: "8.1"

  tasks:
    # Nginx
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
      tags: [install, nginx]

    - name: Configure Nginx
      template:
        src: templates/nginx-site.conf.j2
        dest: /etc/nginx/sites-available/default
        backup: yes
      notify: reload nginx
      tags: [configure, nginx]

    - name: Enable and start Nginx
      service:
        name: nginx
        state: started
        enabled: yes
      tags: [nginx]

    # MySQL
    - name: Install MySQL
      apt:
        name:
          - mysql-server
          - python3-pymysql
        state: present
      tags: [install, mysql]

    - name: Start MySQL
      service:
        name: mysql
        state: started
        enabled: yes
      tags: [mysql]

    - name: Set MySQL root password
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
        state: present
      tags: [configure, mysql]

    - name: Create application database
      mysql_db:
        name: "{{ db_name }}"
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
      tags: [configure, mysql]

    - name: Create database user
      mysql_user:
        name: "{{ db_user }}"
        password: "{{ db_password }}"
        priv: "{{ db_name }}.*:ALL"
        state: present
        login_user: root
        login_password: "{{ mysql_root_password }}"
      tags: [configure, mysql]

    # PHP
    - name: Install PHP and extensions
      apt:
        name:
          - "php{{ php_version }}-fpm"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-curl"
          - "php{{ php_version }}-gd"
          - "php{{ php_version }}-mbstring"
          - "php{{ php_version }}-xml"
          - "php{{ php_version }}-zip"
        state: present
      tags: [install, php]

    - name: Configure PHP-FPM
      template:
        src: templates/php-fpm-pool.conf.j2
        dest: "/etc/php/{{ php_version }}/fpm/pool.d/www.conf"
        backup: yes
      notify: restart php-fpm
      tags: [configure, php]

    - name: Start PHP-FPM
      service:
        name: "php{{ php_version }}-fpm"
        state: started
        enabled: yes
      tags: [php]

    # Application
    - name: Create web root
      file:
        path: /var/www/html
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
      tags: [deploy]

    - name: Deploy PHP application
      copy:
        src: app/
        dest: /var/www/html/
        owner: www-data
        group: www-data
        mode: '0644'
      tags: [deploy]

    - name: Create uploads directory
      file:
        path: /var/www/html/uploads
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
      tags: [deploy]

  handlers:
    - name: reload nginx
      service:
        name: nginx
        state: reloaded

    - name: restart php-fpm
      service:
        name: "php{{ php_version }}-fpm"
        state: restarted

2️⃣ Best Practices

Tổ chức Project Structure

ansible-project/
├── ansible.cfg                 # Ansible configuration
├── inventory/
│   ├── production/
│   │   ├── hosts              # Production inventory
│   │   └── group_vars/
│   │       ├── all.yml
│   │       ├── web.yml
│   │       └── database.yml
│   └── staging/
│       ├── hosts              # Staging inventory
│       └── group_vars/
├── playbooks/
│   ├── site.yml               # Master playbook
│   ├── web.yml                # Web servers playbook
│   └── database.yml           # Database playbook
├── roles/
│   ├── common/
│   ├── nginx/
│   ├── mysql/
│   └── app/
├── group_vars/
│   └── all/
│       ├── vars.yml           # Common variables
│       └── vault.yml          # Encrypted secrets
├── host_vars/
├── files/
├── templates/
└── vars/
    ├── dev.yml
    ├── staging.yml
    └── production.yml

ansible.cfg Configuration

[defaults]
# Inventory location
inventory = ./inventory/production

# Roles path
roles_path = ./roles

# Host key checking
host_key_checking = False

# SSH settings
timeout = 30
forks = 10

# Logging
log_path = ./ansible.log

# Retry files
retry_files_enabled = True
retry_files_save_path = ./retry

# Output
stdout_callback = yaml
bin_ansible_callbacks = True

# Performance
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True

Security với Ansible Vault

# Create encrypted file
ansible-vault create group_vars/all/vault.yml

# Edit encrypted file
ansible-vault edit group_vars/all/vault.yml

# Encrypt existing file
ansible-vault encrypt vars/secrets.yml

# Decrypt file
ansible-vault decrypt vars/secrets.yml

# View encrypted file
ansible-vault view group_vars/all/vault.yml

# Rekey (change password)
ansible-vault rekey group_vars/all/vault.yml

vault.yml:

---
# Database credentials
vault_db_password: "super_secret_password"
vault_mysql_root_password: "another_secret"

# API keys
vault_api_key: "abc123def456"

# SSL certificates
vault_ssl_key: |
  -----BEGIN PRIVATE KEY-----
  ...
  -----END PRIVATE KEY-----

Using vault variables:

vars:
  db_password: "{{ vault_db_password }}"
  api_key: "{{ vault_api_key }}"

Run playbook with vault:

# Ask for vault password
ansible-playbook site.yml --ask-vault-pass

# Use password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass

# Multiple vault passwords
ansible-playbook site.yml --vault-id prod@~/.vault_pass_prod --vault-id staging@~/.vault_pass_staging

Idempotency Best Practices

# ❌ BAD - Not idempotent
- name: Add user
  shell: useradd myuser

# ✅ GOOD - Idempotent
- name: Ensure user exists
  user:
    name: myuser
    state: present

# ❌ BAD - Not idempotent
- name: Append to file
  shell: echo "line" >> /etc/config

# ✅ GOOD - Idempotent
- name: Ensure line in file
  lineinfile:
    path: /etc/config
    line: "line"
    state: present

# ❌ BAD - Not idempotent
- name: Download file
  shell: wget http://example.com/file.tar.gz

# ✅ GOOD - Idempotent
- name: Download file
  get_url:
    url: http://example.com/file.tar.gz
    dest: /tmp/file.tar.gz
    checksum: sha256:abc123...

Error Handling

# Ignore errors
- name: Check if service exists
  command: systemctl status myservice
  register: service_check
  ignore_errors: yes
  changed_when: false

# Custom failure condition
- name: Check disk space
  shell: df -h / | awk 'NR==2 {print $5}' | sed 's/%//'
  register: disk_usage
  failed_when: disk_usage.stdout|int > 90

# Block with rescue
- name: Deploy application
  block:
    - name: Stop application
      service:
        name: myapp
        state: stopped

    - name: Update application
      copy:
        src: app.tar.gz
        dest: /opt/app/

    - name: Start application
      service:
        name: myapp
        state: started

  rescue:
    - name: Rollback
      copy:
        src: /opt/app/backup/
        dest: /opt/app/
        remote_src: yes

    - name: Start old version
      service:
        name: myapp
        state: started

    - name: Send alert
      debug:
        msg: "Deployment failed, rolled back to previous version"

  always:
    - name: Cleanup
      file:
        path: /tmp/deploy.lock
        state: absent

Testing

Syntax Check

ansible-playbook site.yml --syntax-check

Lint với ansible-lint

# Install
pip install ansible-lint

# Check single file
ansible-lint playbook.yml

# Check all playbooks
ansible-lint playbooks/

# With specific rules
ansible-lint --exclude .git playbooks/

.ansible-lint:

---
exclude_paths:
  - .git/
  - .github/
  - roles/external/

skip_list:
  - '106'  # Role name should match pattern
  - '204'  # Lines should be no longer than 160 chars

use_default_rules: true
parseable: true

Dry Run

# Check mode
ansible-playbook site.yml --check

# Check mode with diff
ansible-playbook site.yml --check --diff

# Limit to specific host for testing
ansible-playbook site.yml --limit staging-web-01 --check

Performance Optimization

# Disable fact gathering if not needed
- name: Quick task
  hosts: all
  gather_facts: no
  tasks:
    - name: Ping
      ping:

# Use async for long-running tasks
- name: Long running task
  command: /usr/local/bin/long_script.sh
  async: 3600
  poll: 0
  register: long_task

- name: Check task status
  async_status:
    jid: "{{ long_task.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 30
  delay: 10

# Use strategy: free for independent hosts
- name: Fast deployment
  hosts: all
  strategy: free
  tasks:
    - name: Deploy
      copy:
        src: app/
        dest: /opt/app/

# Increase forks
# In ansible.cfg or command line
# ansible-playbook site.yml -f 20

Documentation

---
# Deploy web application to production
# 
# Prerequisites:
#   - Inventory file configured
#   - Vault password available
#   - Target servers accessible via SSH
#
# Usage:
#   ansible-playbook playbooks/deploy.yml -e "app_version=1.2.3"
#
# Variables:
#   - app_version: Application version to deploy (required)
#   - skip_backup: Skip backup before deployment (default: false)
#
# Tags:
#   - backup: Backup tasks only
#   - deploy: Deployment tasks only
#   - rollback: Rollback deployment
#
# Author: DevOps Team
# Last updated: 2025-11-17

- name: Deploy web application
  hosts: web
  become: yes
  
  vars:
    # Application settings
    app_name: myapp
    app_directory: /opt/{{ app_name }}
    
    # Backup settings
    backup_directory: /backup/{{ app_name }}
    skip_backup: false

💡 Key Takeaways

  • Organize project với proper structure
  • Use Ansible Vault cho sensitive data
  • Ensure idempotency trong tất cả tasks
  • Implement proper error handling
  • Test với syntax check, lint, và dry run
  • Optimize performance với async và strategy
  • Document playbooks cho team

🎯 Bài tập tổng hợp

  • Deploy LEMP stack hoàn chỉnh

    • Nginx với SSL
    • MySQL với secure installation
    • PHP-FPM với optimized config
    • Deploy sample application
  • Implement CI/CD pipeline

    • Automated testing
    • Zero-downtime deployment
    • Rollback capability
    • Monitoring integration
  • Multi-environment setup

    • Separate inventories (dev, staging, prod)
    • Environment-specific variables
    • Ansible Vault for secrets
    • Tags for selective deployment

📚 Tài liệu tham khảo

Official Documentation

Tools