初始化服务器的shell脚本
文件结构
/path/to/your/ansible_project/
├── hosts # Ansible inventory file
├── playbook.yml # 主 Ansible Playbook
└── roles/
└── base_os_setup/ # 定义基础操作系统配置的角色
├── tasks/
│ ├── main.yml # 主要任务入口
│ ├── 01_update_system.yml
│ ├── 02_install_tools.yml
│ ├── 03_configure_ntp.yml
│ ├── 04_configure_ssh.yml
│ ├── 05_create_user.yml
│ ├── 06_manage_selinux.yml
│ └── 07_configure_firewall.yml
├── defaults/
│ └── main.yml # 角色默认变量
└── handlers/
└── main.yml # 处理程序,用于服务重启等
Step 1: hosts 文件 (Ansible Inventory)
# hosts
[new_servers]
your_new_server_ip_or_hostname ansible_user=root ansible_ssh_private_key_file=~/.ssh/your_private_key
Step 2: roles/base_os_setup/defaults/main.yml (角色默认变量)
定义角色中使用的默认配置变量,这样它们可以被 Playbook 或命令行覆盖。
# roles/base_os_setup/defaults/main.yml
---
# 用户相关
new_user_name: devops
new_user_sudo_nopasswd: true # 是否允许新用户免密码sudo
# SSH相关
ssh_port: 22 # SSH监听端口
ssh_permit_root_login: "no" # 是否允许root登录SSH
ssh_password_authentication: "no" # 是否允许密码认证
ssh_pubkey_authentication: "yes" # 是否允许密钥认证
# SELinux相关 (仅CentOS/RHEL)
selinux_state: disabled # disabled, enforcing, permissive
# 防火墙相关
firewall_enabled: true # 是否启用防火墙
firewall_allowed_ports: # 允许通过防火墙的TCP端口列表
- "{{ ssh_port }}/tcp"
- "80/tcp" # HTTP
- "443/tcp" # HTTPS
firewall_allowed_services: # 允许通过防火墙的服务列表 (firewalld支持)
- ssh
# - http
# - https
# 常用工具列表
common_packages:
- vim
- net-tools
- git
- wget
- curl
- rsync
- ntpdate # Ubuntu/Debian
- chrony # CentOS/RHEL
# NTP服务配置
ntp_server: pool.ntp.org
# 是否清理不必要的包
cleanup_packages: true
Step 3: roles/base_os_setup/handlers/main.yml (处理程序)
处理程序是当任务通知它们时才运行的任务,常用于重启服务。
# roles/base_os_setup/handlers/main.yml
---
- name: restart sshd
service:
name: sshd
state: restarted
- name: restart chronyd
service:
name: chronyd
state: restarted
- name: restart ntp
service:
name: ntp
state: restarted
- name: reload firewalld
service:
name: firewalld
state: reloaded
- name: reboot system
reboot:
reboot_timeout: 600
Step 4: roles/base_os_setup/tasks/*.yml (任务定义)
我们将每个初始化步骤拆分到单独的文件中,并在 main.yml 中引用它们。
roles/base_os_setup/tasks/main.yml
这是角色的主入口,按顺序包含其他任务文件。
# roles/base_os_setup/tasks/main.yml
---
- name: Include system update tasks
ansible.builtin.include_tasks: 01_update_system.yml
- name: Include common tools installation tasks
ansible.builtin.include_tasks: 02_install_tools.yml
- name: Include NTP configuration tasks
ansible.builtin.include_tasks: 03_configure_ntp.yml
- name: Include SSH configuration tasks
ansible.builtin.include_tasks: 04_configure_ssh.yml
- name: Include user creation tasks
ansible.builtin.include_tasks: 05_create_user.yml
- name: Include SELinux management tasks
ansible.builtin.include_tasks: 06_manage_selinux.yml
when: ansible_os_family == 'RedHat' # 仅在RedHat系系统上执行
- name: Include firewall configuration tasks
ansible.builtin.include_tasks: 07_configure_firewall.yml
when: firewall_enabled
# 清理包 (可选)
- name: Clean up unnecessary packages (optional)
block:
- name: Clean up for RedHat
ansible.builtin.yum:
autoremove: yes
clean_db: yes
when: ansible_os_family == 'RedHat'
- name: Clean up for Debian
ansible.builtin.apt:
autoremove: yes
clean: yes
when: ansible_os_family == 'Debian'
when: cleanup_packages
roles/base_os_setup/tasks/01_update_system.yml
# roles/base_os_setup/tasks/01_update_system.yml
---
- name: Update all packages (RedHat)
ansible.builtin.yum:
name: "*"
state: latest
update_cache: yes
when: ansible_os_family == 'RedHat'
- name: Update all packages (Debian)
ansible.builtin.apt:
update_cache: yes
upgrade: dist
when: ansible_os_family == 'Debian'
roles/base_os_setup/tasks/02_install_tools.yml
# roles/base_os_setup/tasks/02_install_tools.yml
---
- name: Install EPEL release (RedHat)
ansible.builtin.yum:
name: epel-release
state: present
when: ansible_os_family == 'RedHat'
- name: Install common tools (RedHat)
ansible.builtin.yum:
name: "{{ common_packages }}"
state: present
when: ansible_os_family == 'RedHat'
- name: Install common tools (Debian)
ansible.builtin.apt:
name: "{{ common_packages }}"
state: present
when: ansible_os_family == 'Debian'
roles/base_os_setup/tasks/03_configure_ntp.yml
# roles/base_os_setup/tasks/03_configure_ntp.yml
---
- name: Configure Chrony (RedHat)
block:
- name: Ensure Chrony is installed
ansible.builtin.yum:
name: chrony
state: present
- name: Configure Chrony server
ansible.builtin.lineinfile:
path: /etc/chrony.conf
regexp: '^pool'
line: "pool {{ ntp_server }} iburst"
insertafter: '^#pool'
notify: restart chronyd
- name: Enable and start Chrony service
ansible.builtin.service:
name: chronyd
state: started
enabled: yes
when: ansible_os_family == 'RedHat'
- name: Configure NTP (Debian)
block:
- name: Ensure NTP is installed
ansible.builtin.apt:
name: ntp
state: present
- name: Configure NTP server
ansible.builtin.lineinfile:
path: /etc/ntp.conf
regexp: '^pool'
line: "pool {{ ntp_server }} iburst"
insertafter: '^#pool'
notify: restart ntp
- name: Enable and start NTP service
ansible.builtin.service:
name: ntp
state: started
enabled: yes
when: ansible_os_family == 'Debian'
- name: Sync time once (using ntpdate if available)
ansible.builtin.command: ntpdate -u {{ ntp_server }}
ignore_errors: true # 如果ntpdate不存在或同步失败,不影响后续
changed_when: false # 这个命令本身不会改变系统状态
roles/base_os_setup/tasks/04_configure_ssh.yml
# roles/base_os_setup/tasks/04_configure_ssh.yml
---
- name: Ensure SSH configuration file exists
ansible.builtin.file:
path: /etc/ssh/sshd_config
state: file
mode: '0600'
- name: Set SSH Port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port '
line: "Port {{ ssh_port }}"
notify: restart sshd
- name: Set PermitRootLogin
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin '
line: "PermitRootLogin {{ ssh_permit_root_login }}"
notify: restart sshd
- name: Set PasswordAuthentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication '
line: "PasswordAuthentication {{ ssh_password_authentication }}"
notify: restart sshd
- name: Set PubkeyAuthentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PubkeyAuthentication '
line: "PubkeyAuthentication {{ ssh_pubkey_authentication }}"
notify: restart sshd
roles/base_os_setup/tasks/05_create_user.yml
# roles/base_os_setup/tasks/05_create_user.yml
---
- name: Create a new user
ansible.builtin.user:
name: "{{ new_user_name }}"
state: present
shell: /bin/bash
create_home: true
- name: Set up sudoers for the new user (RedHat)
ansible.builtin.lineinfile:
path: "/etc/sudoers.d/{{ new_user_name }}"
state: present
create: yes
mode: '0440'
line: "{{ new_user_name }} ALL=(ALL) NOPASSWD: ALL"
validate: /usr/sbin/visudo -cf %s
when: ansible_os_family == 'RedHat' and new_user_sudo_nopasswd
- name: Add new user to sudo group (Debian)
ansible.builtin.user:
name: "{{ new_user_name }}"
groups: sudo
append: yes
when: ansible_os_family == 'Debian' and new_user_sudo_nopasswd
# 提示:通常在这里添加用户的SSH公钥
# - name: Add SSH public key for {{ new_user_name }}
# ansible.posix.authorized_key:
# user: "{{ new_user_name }}"
# state: present
# key: "{{ lookup('file', 'files/id_rsa.pub') }}" # 假设公钥文件在files目录下
# key: "ssh-rsa AAAAB3Nz..." # 或者直接在这里写公钥
roles/base_os_setup/tasks/06_manage_selinux.yml
# roles/base_os_setup/tasks/06_manage_selinux.yml
---
- name: Set SELinux state
ansible.posix.selinux:
state: "{{ selinux_state }}"
policy: targeted
register: selinux_status
- name: Reboot if SELinux state changed and needs reboot
ansible.builtin.meta: refresh_inventory # 刷新facts以获取最新系统状态
when: selinux_status.reboot_required is defined and selinux_status.reboot_required
notify: reboot system # 通知处理程序重启
roles/base_os_setup/tasks/07_configure_firewall.yml
# roles/base_os_setup/tasks/07_configure_firewall.yml
---
- name: Configure Firewalld (RedHat)
block:
- name: Ensure firewalld is installed and running
ansible.builtin.service:
name: firewalld
state: started
enabled: yes
- name: Allow specified ports in firewalld
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: yes # 立即生效
loop: "{{ firewall_allowed_ports }}"
- name: Allow specified services in firewalld
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
state: enabled
immediate: yes # 立即生效
loop: "{{ firewall_allowed_services }}"
notify: reload firewalld # 重新加载防火墙配置
when: ansible_os_family == 'RedHat'
- name: Configure UFW (Debian)
block:
- name: Ensure ufw is installed
ansible.builtin.apt:
name: ufw
state: present
- name: Set UFW default policies
community.general.ufw:
default: deny
direction: incoming
state: enabled
- name: Allow specified ports in UFW
community.general.ufw:
rule: allow
port: "{{ item.split('/')[0] }}" # 提取端口号
proto: "{{ item.split('/')[1] }}" # 提取协议
state: enabled
loop: "{{ firewall_allowed_ports }}"
when: ansible_os_family == 'Debian'
Step 5: playbook.yml (主 Ansible Playbook)
现在主 Playbook 变得非常简洁,它只需要调用 base_os_setup 角色。
# playbook.yml
---
- name: Initialize new server with fine-grained control
hosts: new_servers
gather_facts: false # 第一次连接可能没有Python
become: true
pre_tasks: # 在角色执行前,确保Python存在
- name: Ensure Python is installed (for Ansible facts gathering and modules)
ansible.builtin.raw: "test -e /usr/bin/python || (yum install -y python -y || apt update -y && apt install -y python-minimal -y)"
args:
creates: /usr/bin/python
ignore_errors: true
changed_when: false
- name: Wait for connection after python install
ansible.builtin.wait_for_connection:
timeout: 300
- name: Gather facts after python is installed
ansible.builtin.setup:
roles:
- base_os_setup
执行
-
保存所有文件到上述结构中。
-
确保你的Ansible控制机上安装了Ansible。
-
确保你的SSH密钥 (
~/.ssh/your_private_key) 或密码可以连接到新的服务器的root用户。 -
在
ansible_project目录下执行:ansible-playbook -i hosts playbook.yml
优势
- 幂等性 (Idempotency): Ansible 的模块本身就是幂等的。例如,
ansible.builtin.user模块在用户已存在时不会再次创建,ansible.builtin.yum或apt在包已安装最新版时不会再次安装。 - 细粒度控制: 每个任务都专注于一小块功能,更容易理解、测试和调试。
- 可读性和维护性: 代码结构清晰,通过角色和任务文件组织,便于长期维护。变量集中管理在
defaults/main.yml中。 - 错误处理: Ansible 会捕获每个模块的执行结果。如果一个模块失败,Playbook 会停止(除非你指定
ignore_errors: true),提供清晰的错误信息。 - 跨平台兼容性: 通过
when: ansible_os_family == 'RedHat'或'Debian'条件判断,同一个角色可以无缝支持 CentOS/RHEL 和 Ubuntu/Debian。 - 通知 (Handlers): 使用
notify机制,只有当配置文件真正改变时,服务才会重启,提高了效率。 - 可重用性:
base_os_setup角色可以在多个 Playbook 中重用,或者与其他角色结合,构建更复杂的自动化流程。 - Facts 收集:
ansible.builtin.setup:任务在 Python 安装后强制收集一次 facts,确保后续任务能够利用这些信息(如ansible_os_family)。
浙公网安备 33010602011771号