- Tác giả

- Name
- Nguyễn Đức Xinh
- 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
