ansible-coder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAnsible Coder
Ansible 代码编写指南
⚠️ SIMPLICITY FIRST - Default to Flat Structure
⚠️ 简洁优先 - 默认采用扁平化结构
ALWAYS start with the simplest approach. Only add complexity when explicitly requested.
始终从最简单的方案入手。仅在明确要求时才增加复杂度。
Simple (DEFAULT) vs Overengineered
简洁(默认)vs 过度设计
| Aspect | ✅ Simple (Default) | ❌ Overengineered |
|---|---|---|
| Playbooks | 1 playbook with inline tasks | Multiple playbooks + custom roles |
| Roles | Use Galaxy roles (geerlingguy.*) | Write custom roles for simple tasks |
| Inventory | Single | Multiple inventories + group_vars hierarchy |
| Variables | Inline in playbook or single vars file | Scattered across group_vars/host_vars |
| File count | ~3-5 files total | 20+ files in nested directories |
| 维度 | ✅ 简洁(默认) | ❌ 过度设计 |
|---|---|---|
| Playbooks | 1个包含内联任务的Playbook | 多个Playbook + 自定义Roles |
| Roles | 使用Galaxy Roles(geerlingguy.*) | 为简单任务编写自定义Roles |
| 清单 | 单个 | 多个清单 + group_vars层级结构 |
| 变量 | 内联在Playbook中或单个变量文件 | 分散在group_vars/host_vars中 |
| 文件数量 | 总计约3-5个文件 | 嵌套目录中包含20+个文件 |
When to Use Simple Approach (90% of cases)
何时使用简洁方案(90%的场景)
- Setting up 1-5 servers
- Standard stack (Docker, nginx, fail2ban, ufw)
- Single environment or identical servers
- No complex conditional logic per host
- 搭建1-5台服务器
- 标准技术栈(Docker、nginx、fail2ban、ufw)
- 单一环境或配置相同的服务器
- 无需针对单台主机的复杂条件逻辑
When Complexity is Justified (10% of cases)
何时需要复杂度(10%的场景)
- Large fleet with divergent configurations
- Multi-team requiring role isolation
- Complex orchestration with dependencies
- User explicitly requests modular structure
Rule: If you can fit everything in one 200-line playbook, DO IT.
- 配置差异较大的大规模服务器集群
- 多团队协作需要角色隔离
- 存在依赖关系的复杂编排
- 用户明确要求模块化结构
规则:如果所有内容能放入一个200行以内的Playbook,就这么做。
When to Use Ansible vs Cloud-Init
何时选择Ansible 或 Cloud-Init
| Use Cloud-Init When | Use Ansible When |
|---|---|
| First boot only | Re-running config on existing servers |
| Simple package install | Complex multi-step configuration |
| Basic user creation | Role-based configuration |
| Immutable infrastructure | Mutable servers needing updates |
Rule of thumb: Cloud-init for initial provisioning, Ansible for ongoing management.
| 适合使用Cloud-Init的场景 | 适合使用Ansible的场景 |
|---|---|
| 仅首次启动时配置 | 在现有服务器上重新运行配置 |
| 简单的软件包安装 | 复杂的多步骤配置 |
| 基础用户创建 | 基于角色的配置 |
| 不可变基础设施 | 需要更新的可变服务器 |
经验法则:Cloud-Init用于初始预配置,Ansible用于持续管理。
Directory Structure
目录结构
Simple Structure (DEFAULT)
简洁结构(默认)
infra/ansible/
├── playbook.yml # Single playbook with all tasks inline
├── requirements.yml # Galaxy dependencies (geerlingguy.*, etc.)
├── hosts.ini # Inventory (git-ignored)
└── hosts.ini.example # Inventory templateinfra/ansible/
├── playbook.yml # 包含所有内联任务的单个Playbook
├── requirements.yml # Galaxy依赖(如geerlingguy.*)
├── hosts.ini # 清单(已加入git忽略)
└── hosts.ini.example # 清单模板Complex Structure (only when justified)
复杂结构(仅在必要时使用)
infra/ansible/
├── playbook.yml # Main playbook
├── requirements.yml # Galaxy dependencies
├── hosts.ini # Inventory (git-ignored)
├── hosts.ini.example # Inventory template
├── group_vars/
│ └── all.yml # Shared variables
└── roles/
└── custom_role/
├── tasks/main.yml
├── handlers/main.yml
└── templates/infra/ansible/
├── playbook.yml # 主Playbook
├── requirements.yml # Galaxy依赖
├── hosts.ini # 清单(已加入git忽略)
├── hosts.ini.example # 清单模板
├── group_vars/
│ └── all.yml # 共享变量
└── roles/
└── custom_role/
├── tasks/main.yml
├── handlers/main.yml
└── templates/Inventory
清单配置
Static Inventory
静态清单
ini
undefinedini
undefinedhosts.ini
hosts.ini
[web]
192.168.1.1 ansible_user=root
[db]
192.168.1.2 ansible_user=root
[all:vars]
ansible_python_interpreter=/usr/bin/python3
undefined[web]
192.168.1.1 ansible_user=root
[db]
192.168.1.2 ansible_user=root
[all:vars]
ansible_python_interpreter=/usr/bin/python3
undefinedDynamic from Terraform
基于Terraform的动态清单
bash
undefinedbash
undefinedGenerate inventory from Terraform output
从Terraform输出生成清单
SERVER_IP=$(cd infra && tofu output -raw server_ip)
cat > infra/ansible/hosts.ini << EOF
[web]
$SERVER_IP ansible_user=root
EOF
undefinedSERVER_IP=$(cd infra && tofu output -raw server_ip)
cat > infra/ansible/hosts.ini << EOF
[web]
$SERVER_IP ansible_user=root
EOF
undefinedPlaybook Structure
Playbook 结构
Basic Playbook
基础Playbook
yaml
---
- name: Configure web servers
hosts: web
become: true
vars:
timezone: "UTC"
swap_size_mb: "2048"
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install packages
ansible.builtin.apt:
name:
- docker.io
- fail2ban
- ufw
state: presentyaml
---
- name: Configure web servers
hosts: web
become: true
vars:
timezone: "UTC"
swap_size_mb: "2048"
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install packages
ansible.builtin.apt:
name:
- docker.io
- fail2ban
- ufw
state: presentWith Roles
使用Roles的Playbook
yaml
---
- name: Configure web servers
hosts: web
become: true
vars:
security_autoupdate_reboot: true
security_autoupdate_reboot_time: "03:00"
roles:
- role: geerlingguy.swap
when: ansible_swaptotal_mb < 1
- role: geerlingguy.docker
- role: securityyaml
---
- name: Configure web servers
hosts: web
become: true
vars:
security_autoupdate_reboot: true
security_autoupdate_reboot_time: "03:00"
roles:
- role: geerlingguy.swap
when: ansible_swaptotal_mb < 1
- role: geerlingguy.docker
- role: securityCommon Tasks
常见任务
Package Management
软件包管理
yaml
- name: Install required packages
ansible.builtin.apt:
name:
- curl
- ca-certificates
- gnupg
- fail2ban
- ufw
- ntp
state: present
update_cache: trueyaml
- name: Install required packages
ansible.builtin.apt:
name:
- curl
- ca-certificates
- gnupg
- fail2ban
- ufw
- ntp
state: present
update_cache: trueDocker Installation
Docker安装
yaml
- name: Check if Docker is installed
ansible.builtin.command: docker --version
register: docker_installed
ignore_errors: true
changed_when: false
- name: Install Docker via convenience script
ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
when: docker_installed.rc != 0
args:
creates: /usr/bin/docker
- name: Ensure Docker is running
ansible.builtin.systemd:
name: docker
state: started
enabled: trueyaml
- name: Check if Docker is installed
ansible.builtin.command: docker --version
register: docker_installed
ignore_errors: true
changed_when: false
- name: Install Docker via convenience script
ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
when: docker_installed.rc != 0
args:
creates: /usr/bin/docker
- name: Ensure Docker is running
ansible.builtin.systemd:
name: docker
state: started
enabled: trueSSH Hardening
SSH加固
yaml
- name: Disable SSH password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication no"
notify: Restart ssh
- name: Disable SSH root login with password
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin prohibit-password"
notify: Restart ssh
handlers:
- name: Restart ssh
ansible.builtin.systemd:
name: ssh # Ubuntu uses 'ssh', not 'sshd'
state: restartedyaml
- name: Disable SSH password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication no"
notify: Restart ssh
- name: Disable SSH root login with password
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin prohibit-password"
notify: Restart ssh
handlers:
- name: Restart ssh
ansible.builtin.systemd:
name: ssh # Ubuntu使用'ssh'而非'sshd'
state: restartedFail2ban
Fail2ban配置
yaml
- name: Configure fail2ban for SSH
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
mode: "0644"
notify: Restart fail2ban
- name: Ensure fail2ban is running
ansible.builtin.systemd:
name: fail2ban
state: started
enabled: true
handlers:
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restartedyaml
- name: Configure fail2ban for SSH
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
mode: "0644"
notify: Restart fail2ban
- name: Ensure fail2ban is running
ansible.builtin.systemd:
name: fail2ban
state: started
enabled: true
handlers:
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restartedUFW Firewall
UFW防火墙配置
yaml
- name: Set UFW default policies
community.general.ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
- name: Allow specified ports through UFW
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 22 # SSH
- 80 # HTTP
- 443 # HTTPS
- name: Enable UFW
community.general.ufw:
state: enabledyaml
- name: Set UFW default policies
community.general.ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
- name: Allow specified ports through UFW
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 22 # SSH
- 80 # HTTP
- 443 # HTTPS
- name: Enable UFW
community.general.ufw:
state: enabledKernel Tuning
内核调优
yaml
- name: Configure sysctl for performance
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: true
loop:
- { name: vm.swappiness, value: "10" }
- { name: net.core.somaxconn, value: "65535" }yaml
- name: Configure sysctl for performance
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: true
loop:
- { name: vm.swappiness, value: "10" }
- { name: net.core.somaxconn, value: "65535" }Timezone
时区设置
yaml
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"yaml
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"Remove Snap (Ubuntu bloat)
移除Snap(Ubuntu冗余组件)
yaml
- name: Remove snapd
ansible.builtin.apt:
name: snapd
state: absent
purge: true
ignore_errors: true
- name: Remove snap directories
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /snap
- /var/snap
- /var/lib/snapdyaml
- name: Remove snapd
ansible.builtin.apt:
name: snapd
state: absent
purge: true
ignore_errors: true
- name: Remove snap directories
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /snap
- /var/snap
- /var/lib/snapdGalaxy Dependencies
Galaxy 依赖
requirements.yml
requirements.yml
yaml
---
roles:
- name: geerlingguy.swap
version: 2.0.0
- name: geerlingguy.docker
version: 7.4.1
collections:
- name: community.general
- name: ansible.posixyaml
---
roles:
- name: geerlingguy.swap
version: 2.0.0
- name: geerlingguy.docker
version: 7.4.1
collections:
- name: community.general
- name: ansible.posixInstallation
安装方法
bash
ansible-galaxy install -r requirements.yml --forcebash
ansible-galaxy install -r requirements.yml --forceRunning Playbooks
运行Playbook
Basic Execution
基础执行
bash
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.ymlbash
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.ymlWith Variables
传入变量
bash
ansible-playbook -i hosts.ini playbook.yml \
-e "timezone=Europe/Berlin" \
-e "swap_size_mb=4096"bash
ansible-playbook -i hosts.ini playbook.yml \
-e "timezone=Europe/Berlin" \
-e "swap_size_mb=4096"Dry Run
试运行(Dry Run)
bash
ansible-playbook -i hosts.ini playbook.yml --check --diffbash
ansible-playbook -i hosts.ini playbook.yml --check --diffLimit to Specific Hosts
限制到特定主机
bash
ansible-playbook -i hosts.ini playbook.yml --limit webbash
ansible-playbook -i hosts.ini playbook.yml --limit webKamal Server Preparation
Kamal 服务器准备
Complete playbook for Kamal deployment servers (based on kamal-ansible-manager):
yaml
---
- name: Prepare server for Kamal deployment
hosts: web
become: true
vars:
swap_file_size_mb: "2048"
timezone: "UTC"
ufw_allowed_ports: [22, 80, 443]
roles:
- role: geerlingguy.swap
when: ansible_swaptotal_mb < 1
tasks:
# System updates
- name: Update and upgrade packages
ansible.builtin.apt:
update_cache: true
upgrade: dist
# Remove bloat
- name: Remove snapd
ansible.builtin.apt:
name: snapd
state: absent
purge: true
ignore_errors: true
# Essential packages
- name: Install required packages
ansible.builtin.apt:
name: [curl, ca-certificates, fail2ban, ufw, ntp]
state: present
# Docker
- name: Install Docker
ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
args:
creates: /usr/bin/docker
- name: Enable Docker
ansible.builtin.systemd:
name: docker
state: started
enabled: true
# Security
- name: Configure fail2ban
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
maxretry = 5
bantime = 3600
mode: "0644"
notify: Restart fail2ban
- name: Configure UFW
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ ufw_allowed_ports }}"
- name: Enable UFW
community.general.ufw:
state: enabled
policy: deny
direction: incoming
# SSH hardening
- name: Harden SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: "^#?PasswordAuthentication", line: "PasswordAuthentication no" }
- { regexp: "^#?PermitRootLogin", line: "PermitRootLogin prohibit-password" }
notify: Restart ssh
# Performance
- name: Tune kernel
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
reload: true
loop:
- { name: vm.swappiness, value: "10" }
- { name: net.core.somaxconn, value: "65535" }
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"
handlers:
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restarted
- name: Restart ssh
ansible.builtin.systemd:
name: ssh
state: restarted适用于Kamal部署服务器的完整Playbook(基于kamal-ansible-manager):
yaml
---
- name: Prepare server for Kamal deployment
hosts: web
become: true
vars:
swap_file_size_mb: "2048"
timezone: "UTC"
ufw_allowed_ports: [22, 80, 443]
roles:
- role: geerlingguy.swap
when: ansible_swaptotal_mb < 1
tasks:
# System updates
- name: Update and upgrade packages
ansible.builtin.apt:
update_cache: true
upgrade: dist
# Remove bloat
- name: Remove snapd
ansible.builtin.apt:
name: snapd
state: absent
purge: true
ignore_errors: true
# Essential packages
- name: Install required packages
ansible.builtin.apt:
name: [curl, ca-certificates, fail2ban, ufw, ntp]
state: present
# Docker
- name: Install Docker
ansible.builtin.shell: curl -fsSL https://get.docker.com | sh
args:
creates: /usr/bin/docker
- name: Enable Docker
ansible.builtin.systemd:
name: docker
state: started
enabled: true
# Security
- name: Configure fail2ban
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
maxretry = 5
bantime = 3600
mode: "0644"
notify: Restart fail2ban
- name: Configure UFW
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ ufw_allowed_ports }}"
- name: Enable UFW
community.general.ufw:
state: enabled
policy: deny
direction: incoming
# SSH hardening
- name: Harden SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: "^#?PasswordAuthentication", line: "PasswordAuthentication no" }
- { regexp: "^#?PermitRootLogin", line: "PermitRootLogin prohibit-password" }
notify: Restart ssh
# Performance
- name: Tune kernel
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
reload: true
loop:
- { name: vm.swappiness, value: "10" }
- { name: net.core.somaxconn, value: "65535" }
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"
handlers:
- name: Restart fail2ban
ansible.builtin.systemd:
name: fail2ban
state: restarted
- name: Restart ssh
ansible.builtin.systemd:
name: ssh
state: restartedIntegration with Terraform
与Terraform集成
Provision Script Pattern
预配置脚本模板
bash
#!/usr/bin/env bashbash
#!/usr/bin/env bashinfra/bin/provision
infra/bin/provision
1. Terraform creates server
1. Terraform创建服务器
cd infra && tofu apply
SERVER_IP=$(tofu output -raw server_ip)
cd infra && tofu apply
SERVER_IP=$(tofu output -raw server_ip)
2. Wait for SSH
2. 等待SSH可用
until ssh -o ConnectTimeout=5 root@$SERVER_IP true 2>/dev/null; do
sleep 5
done
until ssh -o ConnectTimeout=5 root@$SERVER_IP true 2>/dev/null; do
sleep 5
done
3. Generate inventory
3. 生成清单
echo "[web]\n$SERVER_IP ansible_user=root" > ansible/hosts.ini
echo "[web]\n$SERVER_IP ansible_user=root" > ansible/hosts.ini
4. Run Ansible
4. 运行Ansible
cd ansible
ansible-galaxy install -r requirements.yml
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.yml
cd ansible
ansible-galaxy install -r requirements.yml
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i hosts.ini playbook.yml
5. Kamal bootstrap
5. Kamal初始化
cd ../..
bundle exec kamal server bootstrap
undefinedcd ../..
bundle exec kamal server bootstrap
undefinedTroubleshooting
故障排查
| Issue | Cause | Fix |
|---|---|---|
| Server not ready | Wait or check firewall |
| Wrong SSH key | Specify with |
| User needs NOPASSWD | Use |
| Handler not running | Task didn't change | Use |
| Module not found | Missing collection | Install from requirements.yml |
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 服务器未就绪 | 等待或检查防火墙 |
| SSH密钥错误 | 使用 |
| 用户无NOPASSWD权限 | 使用 |
| 处理器未运行 | 任务未产生变更 | 使用 |
| 模块未找到 | 缺少集合 | 从requirements.yml安装 |