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.
