Site logo

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

5:00 read

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

Yêu cầu đăng nhập

Vui lòng đăng nhập để truy cập nội dung này

Additional Resources

Course Guide

Comprehensive PDF guide with examples

GitHub Repository

Example code for all lessons

Discussion

Have a question about this lesson? Post it here and get answers from instructors and peers.