初始化服务器的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

执行

  1. 保存所有文件到上述结构中。

  2. 确保你的Ansible控制机上安装了Ansible。

  3. 确保你的SSH密钥 (~/.ssh/your_private_key) 或密码可以连接到新的服务器的 root 用户。

  4. ansible_project 目录下执行:

    ansible-playbook -i hosts playbook.yml
    

优势

  1. 幂等性 (Idempotency): Ansible 的模块本身就是幂等的。例如,ansible.builtin.user 模块在用户已存在时不会再次创建,ansible.builtin.yumapt 在包已安装最新版时不会再次安装。
  2. 细粒度控制: 每个任务都专注于一小块功能,更容易理解、测试和调试。
  3. 可读性和维护性: 代码结构清晰,通过角色和任务文件组织,便于长期维护。变量集中管理在 defaults/main.yml 中。
  4. 错误处理: Ansible 会捕获每个模块的执行结果。如果一个模块失败,Playbook 会停止(除非你指定 ignore_errors: true),提供清晰的错误信息。
  5. 跨平台兼容性: 通过 when: ansible_os_family == 'RedHat''Debian' 条件判断,同一个角色可以无缝支持 CentOS/RHEL 和 Ubuntu/Debian。
  6. 通知 (Handlers): 使用 notify 机制,只有当配置文件真正改变时,服务才会重启,提高了效率。
  7. 可重用性: base_os_setup 角色可以在多个 Playbook 中重用,或者与其他角色结合,构建更复杂的自动化流程。
  8. Facts 收集: ansible.builtin.setup: 任务在 Python 安装后强制收集一次 facts,确保后续任务能够利用这些信息(如 ansible_os_family)。
posted on 2025-08-09 17:44  Leo-Yide  阅读(34)  评论(0)    收藏  举报