Ansible第六天任务
在Ansible项目中,如何拆分Playbook,主Playbook如何导入其他Playbook
在大型Ansible项目中,为了提高可读性、可维护性和重用性,拆分Playbook是必不可少的。Ansible提供了多种机制来实现Playbook的拆分和导入。
拆分Playbook的常见策略
- 按功能拆分: 将相关的任务组合成独立的Playbook或Roles。例如,一个Playbook负责Web服务器的配置,另一个负责数据库的配置。
- 按环境拆分: 为不同的环境(如开发、测试、生产)创建不同的变量文件或Playbook,以适应环境差异。
- 按组件拆分: 如果一个项目包含多个独立的服务或应用程序,可以为每个服务创建一个独立的Playbook或Role。
- 使用Roles: Roles是Ansible中组织内容的推荐方式。它将Playbook、任务、变量、模板、文件和处理器等组织成一个定义良好的目录结构,非常适合模块化和重用。
主Playbook如何导入其他Playbook
Ansible提供了两种主要的方式来在主Playbook中导入其他Playbook或任务:
1. 使用
import_playbook
(导入整个Playbook)import_playbook
用于导入另一个完整的Playbook。被导入的Playbook会像它自己被调用一样执行,包括其所有的Plays、Tasks、Handlers等。示例:
假设你有两个子Playbook:
webservers.yml
和databases.yml
。webservers.yml
:YAML
--- - name: Configure web servers hosts: web become: yes tasks: - name: Install Nginx ansible.builtin.apt: name: nginx state: present - name: Start Nginx service ansible.builtin.service: name: nginx state: started
databases.yml
:YAML
--- - name: Configure database servers hosts: db become: yes tasks: - name: Install MySQL ansible.builtin.apt: name: mysql-server state: present - name: Start MySQL service ansible.builtin.service: name: mysql state: started
主Playbook (****
site.yml
):YAML
--- - name: Main playbook to deploy application hosts: all - import_playbook: webservers.yml - import_playbook: databases.yml
运行主Playbook:
Bash
ansible-playbook site.yml
注意事项:
import_playbook
在Playbook解析阶段执行,这意味着它在执行任何任务之前就处理了被导入的Playbook。- 它会导入被导入Playbook中定义的所有Play。
- 通常用于将大型项目分解为更小的、独立的Playbook模块。
2. 使用
include_tasks
(导入任务列表) 或import_tasks
(静态导入任务列表)include_tasks
和import_tasks
用于在当前的Play中导入一个任务列表。它们之间的主要区别在于它们的执行时机和动态性。import_tasks
(静态导入): 在Playbook解析阶段处理。它更像是一个复制粘贴操作,将文件中的任务直接插入到当前位置。这意味着它不支持循环(loop
)或条件(when
)来动态决定是否导入任务。include_tasks
(动态导入): 在任务执行阶段处理。它允许你使用循环(loop
)或条件(when
)来动态地导入任务列表。这使得它在需要根据运行时条件灵活加载任务时非常有用。
示例:
假设你有一个任务文件
common_tasks.yml
:common_tasks.yml
:YAML
--- - name: Update apt cache ansible.builtin.apt: update_cache: yes - name: Install common packages ansible.builtin.apt: name: - vim - git state: present
主Playbook (****
main.yml
):使用
import_tasks
:YAML
--- - name: Main playbook with imported tasks hosts: all become: yes tasks: - name: This is a task before importing ansible.builtin.debug: msg: "Starting common tasks import" - import_tasks: common_tasks.yml - name: This is a task after importing ansible.builtin.debug: msg: "Finished common tasks import"
使用
include_tasks
(带条件):YAML
--- - name: Main playbook with included tasks hosts: all become: yes vars: install_common_packages: true # Can be dynamic based on inventory/facts tasks: - name: This is a task before including ansible.builtin.debug: msg: "Starting common tasks include" - include_tasks: common_tasks.yml when: install_common_packages - name: This is a task after including ansible.builtin.debug: msg: "Finished common tasks include"
运行主Playbook:
Bash
ansible-playbook main.yml
选择
import_tasks
还是include_tasks
?- 如果需要编译时(解析阶段)的确定性行为,并且不需要动态循环或条件导入,请使用
import_tasks
。 这通常是更推荐的选项,因为它更易于调试和理解执行流程。 - 如果需要在运行时根据变量、循环或条件动态地加载任务,请使用
include_tasks
。 例如,当你需要为每个项目迭代一组任务时。
3. 使用
Roles
(最佳实践)Roles是Ansible中组织和重用内容的最强大和推荐的方式。它提供了一个预定义的目录结构,用于存放Playbook、任务、变量、模板、文件、处理器等。
Roles的结构示例:
my_role/ ├── defaults/ │ └── main.yml ├── handlers/ │ └── main.yml ├── tasks/ │ └── main.yml ├── templates/ ├── files/ ├── vars/ │ └── main.yml └── meta/ └── main.yml
^^创建Role (以Nginx为例):
Bash
ansible-galaxy init nginx_webserver
这会创建一个
nginx_webserver
目录,包含上述结构。nginx_webserver/tasks/main.yml
:YAML
--- - name: Install Nginx ansible.builtin.apt: name: nginx state: present - name: Start Nginx service ansible.builtin.service: name: nginx state: started
主Playbook (****
site.yml
) 中使用Role:YAML
--- - name: Deploy web application with roles hosts: web become: yes roles: - nginx_webserver
运行主Playbook:
Bash
ansible-playbook site.yml -i inventory.ini
Roles的优势:
- 模块化和可重用性: 将相关任务、变量等封装在一起,便于在不同项目中复用。
- 清晰的结构: 统一的目录结构使得项目更易于理解和导航。
- 变量管理: Role可以有自己的默认变量,这些变量可以在Playbook或命令行中被覆盖。
- 依赖管理: Roles可以通过
meta/main.yml
文件定义依赖关系,确保在执行前正确安装其他Roles。
总结与建议
- 小型项目或简单任务: 可以考虑使用
import_playbook
来拆分主要的逻辑块。 - 需要在运行时动态加载任务: 使用
include_tasks
。 - 静态地将一组任务插入到Play中: 使用
import_tasks
。 - 大型、复杂或需要高度重用的项目: 强烈推荐使用Roles。 它们是Ansible组织和管理自动化内容的最佳实践,能够极大地提高项目的可维护性和可扩展性。
在Ansible项目中,在哪个目录中定义主机节点
在Ansible项目中,主机节点(Managed Nodes)的定义通常是在 Inventory 文件中。
Inventory 文件 是Ansible用来识别和连接到你想要管理的服务器(主机节点)的核心文件。它包含了你所有服务器的列表,并且可以对这些服务器进行分组,以及定义与它们相关的变量。
Inventory 文件的常见位置和命名
默认位置: **
/etc/ansible/hosts
**这是Ansible默认查找Inventory文件的位置。如果你不指定-i
选项,Ansible就会尝试加载这个文件。项目自定义位置: 在大多数实际项目中,你不会使用全局的
/etc/ansible/hosts
文件,而是为每个项目或环境创建一个独立的Inventory文件。项目根目录下的文件: 通常,你会在你的Ansible项目根目录下创建一个名为
inventory.ini
或hosts
的文件来定义主机。my_ansible_project/ ├── inventory.ini <-- 在这里定义主机节点 ├── site.yml ├── roles/ │ └── my_role/ │ └── tasks/ │ └── main.yml └── group_vars/ └── webservers.yml
inventory/
子目录: 对于更复杂的项目,特别是当你需要区分不同环境(如开发、测试、生产)时,最佳实践是创建一个inventory
目录,并在其中为每个环境创建独立的Inventory文件。my_ansible_project/ ├── inventory/ │ ├── production <-- 生产环境的主机清单 │ ├── staging <-- 预发布环境的主机清单 │ └── development <-- 开发环境的主机清单 ├── site.yml └── roles/
在这种情况下,你可以在运行
ansible-playbook
命令时使用-i
选项指定具体的Inventory文件,例如:Bashansible-playbook -i inventory/production site.yml
Inventory 文件的格式
Ansible Inventory文件支持多种格式,最常见的是 INI 格式 和 YAML 格式。
1. INI 格式 (最常用)
INI格式简单直观,适用于大多数情况。
示例
inventory.ini
:Ini, TOML
# 定义单个主机 localhost ansible_connection=local # 定义主机组 [webservers] web1.example.com web2.example.com [databases] db1.example.com db2.example.com # 定义带变量的主机 [appservers] app1.example.com ansible_port=2222 app2.example.com ansible_user=deploy_user # 定义主机范围 [routers] router[01:03].example.com # 会扩展为 router01.example.com, router02.example.com, router03.example.com # 嵌套组 (将webservers和databases归入app_tier组) [app_tier:children] webservers databases # 组变量 (应用于该组下的所有主机) [webservers:vars] http_port=80 max_clients=200 # 所有主机都适用的变量 (all是内置的特殊组) [all:vars] ansible_ssh_private_key_file=~/.ssh/id_rsa
2. YAML 格式
YAML格式在需要更复杂的数据结构(如嵌套变量)时非常有用,但对于简单的清单可能会显得冗长。
示例
inventory.yml
:YAML
all: hosts: localhost: ansible_connection: local children: webservers: hosts: web1.example.com: web2.example.com: vars: http_port: 80 max_clients: 200 databases: hosts: db1.example.com: db2.example.com: appservers: hosts: app1.example.com: ansible_port: 2222 app2.example.com: ansible_user: deploy_user app_tier: children: webservers: databases: vars: ansible_ssh_private_key_file: ~/.ssh/id_rsa
定义主机节点的方式
在Inventory文件中,你可以通过以下方式定义主机节点:
直接列出主机名或IP地址:
server1.example.com 192.168.1.10
使用主机组: 将相关主机分组,这样可以在Playbook中针对整个组执行任务。Ini, TOML
[webservers] web1.example.com web2.example.com
定义主机变量: 为特定主机或主机组定义变量,这些变量可以在Playbook中使用。Ini, TOML
web1.example.com ansible_port=2222 [webservers:vars] nginx_config_path=/etc/nginx/nginx.conf
嵌套组: 组可以包含其他组,形成层级结构。Ini, TOML
[europe:children] webservers_paris webservers_london
动态Inventory: 对于云环境(如AWS, Azure, GCP)或VMware等,你可以使用动态Inventory脚本,这些脚本能根据实时API查询自动生成主机清单,无需手动维护。
总之,主机节点主要在 Inventory 文件 中定义。选择INI或YAML格式取决于你的偏好和项目的复杂性。对于大多数Ansible项目来说,清晰、组织良好的Inventory文件是成功自动化的关键。
在Ansible项目中,如何创建角色及相关目录,命令是什么
在Ansible项目中创建角色(Role)及其相关的目录结构,最常用且推荐的命令是使用
ansible-galaxy init
。1.
ansible-galaxy init
命令这个命令是Ansible官方提供的一个脚手架工具,用于快速生成一个符合Ansible角色最佳实践的目录结构。
基本用法:
Bash
ansible-galaxy init <role_name>
示例:
假设你正在一个Ansible项目目录下,想要创建一个名为
nginx_webserver
的角色:Bash
cd /path/to/your/ansible_project ansible-galaxy init nginx_webserver
运行这个命令后,
nginx_webserver
角色会在当前目录下被创建,并包含以下标准的目录结构:nginx_webserver/ ├── defaults/ │ └── main.yml # 默认变量,优先级最低,容易被覆盖 ├── handlers/ │ └── main.yml # 处理器,用于触发对服务重启或配置文件重载等操作 ├── tasks/ │ └── main.yml # 角色主要的任务定义文件 ├── templates/ │ # 存放Jinja2模板文件,通常用于生成配置文件 ├── files/ │ # 存放静态文件,如脚本、软件包等,通常通过 copy 或 synchronize 模块分发 ├── vars/ │ └── main.yml # 角色变量,优先级高于 defaults,但低于 group_vars/host_vars ├── meta/ │ └── main.yml # 角色元数据,如作者、描述、依赖关系等 └── README.md # 角色说明文件
2. 各目录的作用说明
defaults/
:main.yml
: 存放角色的默认变量。这些变量的优先级最低,意味着它们可以很容易地被更高优先级的变量(如在Playbook中定义的变量、group_vars
、host_vars
、命令行参数-e
等)覆盖。适合定义角色通用的配置项,以便用户可以按需修改。
handlers/
:main.yml
: 存放处理器。处理器是特殊的任务,它们只有在被其他任务通过notify
触发时才会执行。通常用于服务重启、重载配置等操作,以确保只有在必要时才执行这些耗时或中断性的操作。
tasks/
:main.yml
: 这是角色的核心。它包含了该角色所需执行的所有任务定义(例如,安装软件包、配置服务、创建用户等)。你可以在这里使用include_tasks
或import_tasks
来进一步拆分任务文件,以提高可读性。
templates/
:- 存放Jinja2模板文件。这些文件通常包含变量和逻辑,Ansible会在目标主机上渲染它们以生成最终的配置文件。例如,你可以有一个
nginx.conf.j2
文件,其中包含动态端口或服务器名称的变量。
- 存放Jinja2模板文件。这些文件通常包含变量和逻辑,Ansible会在目标主机上渲染它们以生成最终的配置文件。例如,你可以有一个
files/
:- 存放静态文件。这些文件不需要进行模板渲染,会直接复制到目标主机。例如,一个启动脚本、一个预编译的二进制文件、一个SSL证书等。通常通过
ansible.builtin.copy
模块来使用。
- 存放静态文件。这些文件不需要进行模板渲染,会直接复制到目标主机。例如,一个启动脚本、一个预编译的二进制文件、一个SSL证书等。通常通过
vars/
:main.yml
: 存放角色的变量,这些变量的优先级高于defaults
目录中的变量。适合定义角色内部使用的、不希望轻易被外部覆盖的变量,或者特定于该角色的、不属于默认配置的变量。
meta/
:main.yml
: 存放角色的元数据。包括作者信息、描述、许可证、平台兼容性以及最重要的——角色依赖。你可以在这里定义当前角色依赖的其他角色,Ansible会在执行前自动安装或验证这些依赖。
README.md
:- 角色的说明文档。强烈建议在这里详细描述角色的功能、如何使用、可配置的变量以及任何注意事项。
3. 创建角色的位置
通常,角色会存放在你的Ansible项目根目录下的一个名为
roles/
的子目录中。my_ansible_project/ ├── site.yml ├── inventory.ini ├── roles/ <-- 角色通常存放在这里 │ ├── nginx_webserver/ <-- 你使用 ansible-galaxy init 创建的角色 │ │ ├── defaults/ │ │ ├── handlers/ │ │ ├── tasks/ │ │ ├── ... │ └── database_server/ │ ├── defaults/ │ ├── ... ├── group_vars/ └── host_vars/
然后在你的Playbook (
site.yml
或其他主Playbook) 中通过roles:
指令来调用这些角色:YAML
--- - name: Deploy web application hosts: webservers become: yes roles: - nginx_webserver
总结
创建Ansible角色及其目录的命令是
ansible-galaxy init <role_name>
。这个命令为你提供了一个符合最佳实践的起点,极大地简化了角色开发的初始步骤,并帮助你以模块化、可重用的方式组织自动化代码。Playbook中如何指定执行角色及执行节点
在Ansible Playbook中,指定执行角色和执行节点(主机)是核心功能,通过Playbook顶层的几个关键字来实现。
1. 指定执行节点 (Hosts)
在Playbook中,使用
hosts
关键字来指定Playbook将运行在哪些主机或主机组上。这些主机或主机组必须在你的Inventory文件中定义。示例:
YAML
--- - name: Configure web servers hosts: webservers # 指定在名为 'webservers' 的组上执行 become: yes # 启用特权升级 (sudo/su) tasks: - name: Install Nginx ansible.builtin.apt: name: nginx state: present - name: Configure database servers hosts: databases # 指定在名为 'databases' 的组上执行 become: yes tasks: - name: Install MySQL ansible.builtin.apt: name: mysql-server state: present
hosts
关键字的常用值:组名:
hosts: webservers
(推荐,将任务应用于webservers
组中的所有主机)多个组名(逗号分隔):
hosts: webservers,databases
(应用于这两个组中的所有主机)单个主机名或IP:
hosts: web1.example.com
(只应用于指定主机)all
:hosts: all
(应用于Inventory文件中定义的所有主机)排除主机/组:
hosts: webservers:!web2.example.com
(应用于webservers
组,但排除web2.example.com
)hosts: all:!ungrouped
(应用于所有主机,但排除未分组的主机)
交集:
hosts: webservers:&us_regions
(应用于同时属于webservers
和us_regions
的主机)模式匹配:
hosts: web*
(应用于所有名称以web
开头的主机)
2. 指定执行角色 (Roles)
在Playbook中,使用
roles
关键字来指定在特定主机或主机组上执行一个或多个角色。角色会按照它们在roles
列表中出现的顺序执行。示例:
假设你有一个
nginx_webserver
角色和一个mysql_database
角色。YAML
--- - name: Deploy web application hosts: webservers # 指定在 'webservers' 组上执行 become: yes roles: - nginx_webserver # 执行 nginx_webserver 角色 - name: Deploy database hosts: databases # 指定在 'databases' 组上执行 become: yes roles: - mysql_database # 执行 mysql_database 角色
在
roles
中传递变量:你可以在指定角色时传递特定的变量,这些变量的优先级会高于角色的
defaults
和vars
目录中定义的变量。YAML
--- - name: Configure Nginx with custom port hosts: webservers become: yes roles: - role: nginx_webserver nginx_http_port: 8080 # 覆盖角色内部定义的 http_port 变量 nginx_ssl_enabled: true # 启用 SSL
条件性执行角色 (使用
when
和tags
):虽然
roles
关键字本身不支持when
条件(因为角色是Playbook结构的一部分,而不是一个任务),但你可以将整个Play包裹在when
条件中,或者在角色内部的任务中使用when
。更常见和推荐的做法是,在Playbook中为一个Play指定
hosts
,然后在这个Play内部针对所有主机执行角色。如果需要对不同的主机子集执行不同的角色或角色配置,通常会创建多个Play,每个Play对应一个hosts
和roles
组合。示例:多个Play来满足不同需求
YAML
--- - name: Setup production webservers hosts: production_webservers become: yes roles: - nginx_webserver - name: Setup staging webservers hosts: staging_webservers become: yes roles: - nginx_webserver nginx_env: "staging" # 为预发布环境传递特定变量
3. Playbook的结构总结
一个典型的Ansible Playbook由一个或多个 "Play" 组成,每个Play都包含以下关键部分:
YAML
--- - name: <Play的描述性名称> hosts: <指定执行的主机或主机组,必填> become: <yes/no, 是否启用特权升级,可选> vars: # 可选:定义Play级别的变量 some_variable: value roles: # 可选:要执行的角色列表 - role_name_1 - role_name_2 tasks: # 可选:要执行的任务列表 (如果使用了roles,通常tasks部分会很少或没有) - name: <任务描述> module_name: <模块参数> handlers: # 可选:处理器定义 (通常在 roles 或 tasks 中通过 notify 触发) - name: <处理器描述> service: name: service_name state: restarted
在运行Playbook时,Ansible会按照定义的Play顺序执行,并在每个Play中根据
hosts
关键字连接到目标节点,然后执行roles
或tasks
中定义的操作。角色中在哪个目录中编写task
在Ansible角色中,任务(tasks) 应该编写在
tasks/
目录 下。
tasks/
目录及其main.yml
文件当你使用
ansible-galaxy init <role_name>
命令创建角色时,它会自动生成这个目录结构:my_role/ ├── defaults/ ├── handlers/ ├── tasks/ │ └── main.yml <-- 在这里编写你的任务! ├── templates/ ├── files/ ├── vars/ ├── meta/ └── README.md
tasks/
目录中最重要的文件是main.yml
。这个文件是 Ansible 在执行该角色时会查找并运行的默认任务列表。示例:
tasks/main.yml
的内容在这个文件中,你会定义角色的主要逻辑,例如安装软件包、配置服务、管理用户等。
YAML
# my_role/tasks/main.yml --- - name: Update apt cache ansible.builtin.apt: update_cache: yes - name: Install Nginx package ansible.builtin.apt: name: nginx state: present - name: Ensure Nginx service is running and enabled ansible.builtin.service: name: nginx state: started enabled: true - name: Copy Nginx configuration file ansible.builtin.template: src: nginx.conf.j2 # 这个模板文件会在 templates/ 目录下 dest: /etc/nginx/nginx.conf notify: Restart Nginx # 当配置文件发生变化时,通知处理器重启 Nginx
拆分
tasks/
目录下的任务如果你的角色任务很多,为了提高可读性和管理性,你可以在
tasks/
目录下创建多个.yml
文件,并在main.yml
中使用include_tasks
或import_tasks
来引用它们。示例:
my_role/ ├── tasks/ │ ├── main.yml │ ├── install.yml # 安装相关任务 │ ├── config.yml # 配置相关任务 │ └── service.yml # 服务管理相关任务 └── ...
my_role/tasks/main.yml
(引用其他任务文件):YAML
--- - name: Run installation tasks ansible.builtin.import_tasks: install.yml - name: Run configuration tasks ansible.builtin.import_tasks: config.yml - name: Run service management tasks ansible.builtin.import_tasks: service.yml
这样,你的任务列表会更加模块化和易于管理。
所以,总的来说,当你需要编写角色要执行的具体操作时,请将它们放在角色的
tasks/main.yml
文件中。如何拆分task,主task中如何导入其他task
在Ansible项目中,拆分任务(Tasks)是为了提高可读性、可维护性和重用性,尤其当一个角色或一个Play包含大量任务时。Ansible提供了两种主要方式在主任务文件中导入其他任务文件:
import_tasks
和include_tasks
。拆分Task的策略
- 按功能拆分: 将相关的任务组合成独立的文件。例如:
install.yml
(安装软件)、configure.yml
(配置服务)、firewall.yml
(防火墙设置) 等。 - 按阶段拆分: 如果任务有明确的执行顺序或依赖关系,可以按阶段拆分,例如:
pre_checks.yml
、deployment.yml
、post_deployment.yml
。 - 针对特定操作系统或发行版: 使用条件语句在主任务文件中根据目标主机的操作系统或发行版导入不同的任务文件。
主Task中导入其他Task的方法
主要有两种指令用于导入任务:
import_tasks
和include_tasks
。理解它们之间的区别至关重要。1.
import_tasks
(静态导入 - Parse Time)import_tasks
在 Playbook解析阶段(parse time) 执行。它更像是一个宏或复制粘贴操作,Ansible在解析Playbook时会将导入的任务文件内容直接插入到当前位置。特点:
- 静态性: 在Playbook运行前就已经确定了所有任务。
- 不支持循环和条件: 你不能在
import_tasks
语句上使用loop
、when
或with_
循环来动态地决定是否导入文件或导入多少次。 - 调试友好: 因为所有任务都是在解析时确定的,所以调试起来更直接,错误通常在运行前就能发现。
- 性能: 通常比
include_tasks
略快,因为它避免了运行时动态加载的开销。
何时使用:
当你需要将一个大型任务列表分解成更小的、逻辑上独立的文件,并且这些文件的导入逻辑是固定的(不依赖于运行时变量或条件)时,使用
import_tasks
。示例:
假设你在一个角色中,
tasks/main.yml
是主任务文件。你希望将安装和配置任务拆分到单独的文件中。my_role/tasks/install.yml
:YAML
# tasks/install.yml --- - name: Update apt cache (if Debian/Ubuntu) ansible.builtin.apt: update_cache: yes when: ansible_os_family == "Debian" - name: Install Apache web server ansible.builtin.apt: name: apache2 state: present when: ansible_os_family == "Debian" - name: Install httpd web server ansible.builtin.yum: name: httpd state: present when: ansible_os_family == "RedHat"
my_role/tasks/configure.yml
:YAML
# tasks/configure.yml --- - name: Copy Apache configuration file ansible.builtin.template: src: apache.conf.j2 dest: /etc/apache2/apache2.conf notify: Restart Apache when: ansible_os_family == "Debian" - name: Copy httpd configuration file ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: Restart httpd when: ansible_os_family == "RedHat"
my_role/tasks/main.yml
(主任务文件):YAML
# tasks/main.yml --- - name: Import installation tasks ansible.builtin.import_tasks: install.yml - name: Import configuration tasks ansible.builtin.import_tasks: configure.yml - name: Ensure Apache/httpd service is running ansible.builtin.service: name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}" state: started enabled: true
2.
include_tasks
(动态导入 - Run Time)include_tasks
在 Playbook执行阶段(run time) 执行。它在每次任务执行到该行时才会处理被引用的文件。特点:
- 动态性: 支持在
include_tasks
语句上使用loop
、when
或with_
循环。这意味着你可以根据运行时条件或循环迭代来决定是否导入文件,以及导入多少次。 - 灵活性: 适用于需要根据变量或条件动态调整任务流程的场景。
- 调试相对复杂: 因为任务列表是在运行时动态构建的,所以调试可能不如静态导入直观。
- 性能: 理论上会略慢于
import_tasks
,因为每次动态加载都有一些开销。
何时使用:
当你需要根据运行时条件、循环迭代或变量值来动态地决定要执行哪些任务文件时,使用
include_tasks
。示例:
假设你有一个任务文件
manage_service.yml
,你希望根据一个变量services_to_manage
动态地管理多个服务。my_role/tasks/manage_service.yml
:YAML
# tasks/manage_service.yml --- - name: Ensure service {{ item.name }} is {{ item.state }} ansible.builtin.service: name: "{{ item.name }}" state: "{{ item.state }}" enabled: "{{ item.enabled | default(true) }}"
my_role/tasks/main.yml
(主任务文件,使用include_tasks
循环):YAML
# tasks/main.yml --- - name: Define services to manage ansible.builtin.set_fact: services_to_manage: - { name: "nginx", state: "started" } - { name: "php-fpm", state: "started", enabled: false } # 示例:动态传递 enabled 变量 - name: Dynamically manage services ansible.builtin.include_tasks: manage_service.yml loop: "{{ services_to_manage }}" loop_control: loop_var: item # 定义循环变量名为 item
总结与选择建议
推荐策略:
- 默认情况下,优先使用
import_tasks
。 它提供了更好的性能和更清晰的执行流程,因为所有任务在Playbook解析时就已确定。 - 仅当你确实需要动态地加载任务(例如,基于变量循环、条件判断)时,才使用
include_tasks
。
- 按功能拆分: 将相关的任务组合成独立的文件。例如:
角色中的变量要在哪个目录中定义
在Ansible角色中,变量的定义位置非常重要,因为它涉及到变量的优先级。Ansible有一个明确的变量优先级顺序,理解这一点对于正确管理配置至关重要。
在角色中,定义变量的两个主要目录是:
defaults/
目录vars/
目录
下面详细解释这两个目录及其在变量优先级中的位置。
1.
defaults/
目录文件位置:
my_role/defaults/main.yml
作用: 这个目录用于定义角色的默认变量。这些变量的优先级是所有变量中最低的。
优先级: 最低。这意味着它们可以被几乎所有其他来源的变量(例如:
vars/
、group_vars/
、host_vars/``、Playbook中的变量、命令行
-e` 选项)轻松覆盖。使用场景:
- 定义角色通用的、用户可以自定义的配置项。
- 为角色提供合理的默认值,即使外部没有明确提供这些变量,角色也能正常运行。
- 作为角色提供给其他用户的配置指南。
示例:****
my_role/defaults/main.yml
YAML
# my_role/defaults/main.yml --- nginx_http_port: 80 # Nginx 默认监听端口 nginx_ssl_enabled: false # 默认不启用 SSL nginx_max_connections: 1024 # 默认最大连接数 application_version: "1.0.0" # 默认应用版本
2.
vars/
目录文件位置:
my_role/vars/main.yml
作用: 这个目录用于定义角色的内部变量。这些变量的优先级高于
defaults/
目录中的变量。优先级: 高于
defaults/
,但低于group_vars/
、host_vars/
、Playbook中的变量、命令行-e
选项。使用场景:
- 定义角色内部使用的、不希望被轻易覆盖的变量。
- 存放那些与角色功能紧密耦合,不适合作为用户可配置项的变量。
- 定义一些基于
ansible_facts
计算出的变量(虽然这类通常在tasks
中用set_fact
更常见)。
示例:****
my_role/vars/main.yml
YAML
# my_role/vars/main.yml --- # Nginx 服务名称,通常根据操作系统家族设置,不建议轻易修改 nginx_service_name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}" # Nginx 配置目录,也通常根据操作系统家族设置 nginx_conf_dir: "{{ '/etc/apache2' if ansible_os_family == 'Debian' else '/etc/httpd/conf' }}"
注意: 上述
nginx_service_name
和nginx_conf_dir
的例子在实际中更常通过set_fact
动态设置,或者直接在模板中使用ansible_os_family
。这里仅作为vars/
目录存储内部变量的示例。总结
defaults/main.yml
: 存放可以被用户或更高优先级变量覆盖的默认配置。vars/main.yml
: 存放角色内部使用、不希望被轻易覆盖的内部变量。
根据你的变量是作为角色的可配置项还是角色的内部实现细节,来选择在
defaults/
还是vars/
中定义它们。遵循这一实践将使你的Ansible项目更具模块化和可维护性。变量的优先级是怎样的,如何强制覆盖变量
Ansible 变量优先级顺序 (从低到高)
以下是 Ansible 变量优先级的常见顺序,从最低(最容易被覆盖)到最高(最高优先级):
role/defaults/main.yml
: 角色默认变量。这是角色的“出厂设置”,优先级最低,旨在作为用户可以轻松覆盖的值。Inventory 定义的变量:
- Inventory 文件或脚本中的组变量: 例如在
inventory.ini
中[webservers:vars]
部分定义的变量。 - Inventory
group_vars/all
: 在 Inventory 目录下的group_vars/all.yml
定义的变量。 - Inventory
group_vars/<group_name>
: 在 Inventory 目录下的group_vars/<group_name>.yml
定义的变量。 - Inventory 文件或脚本中的主机变量: 例如在
inventory.ini
中主机行后面定义的变量。 - Inventory
host_vars/<host_name>
: 在 Inventory 目录下的host_vars/<host_name>.yml
定义的变量。
- Inventory 文件或脚本中的组变量: 例如在
Facts (收集到的事实) : 通过
gather_facts
任务收集到的主机信息,例如ansible_os_family
、ansible_distribution
等。Playbook 中定义的变量:
vars
(Playbookvars:
节中定义的变量)。vars_prompt
(通过交互式提示获取的变量)。vars_files
(通过vars_files:
节导入的变量文件)。
role/vars/main.yml
: 角色内部变量。优先级高于defaults/
中的变量。set_fact
或register
任务设置的变量: 在Playbook执行过程中,通过set_fact
模块动态创建或通过register
模块注册任务输出的变量。这些变量通常只在它们被定义后的后续任务中生效。Block 变量: 仅对特定
block
块中的任务生效。Task 变量: 仅对特定任务生效,通常通过
vars:
关键字直接定义在任务下面。传递给角色的变量 (Role Parameters) : 当你在Playbook中调用角色时,通过
role:
关键字传递给角色的变量。例如:YAMLroles: - role: my_role my_variable: "value_from_playbook_role_param"
include_vars
模块加载的变量: 通过include_vars
模块在运行时加载的变量。Extra Variables (
-e
或--extra-vars
) : 从命令行传递的额外变量。这是最高优先级的变量来源,几乎可以覆盖所有其他变量。
记忆简化版(从低到高):
- Role defaults
- Inventory 变量 (group_vars, host_vars)
- Facts
- Playbook vars
- Role vars
- Set_fact/Registered vars
- Extra vars (
-e
)
如何强制覆盖变量
由于Ansible的变量优先级机制,通常你只需要在更高优先级的位置定义变量,就可以覆盖较低优先级的变量。最高优先级是 命令行
-e
或--extra-vars
选项。示例:强制覆盖变量
假设你在
my_role/defaults/main.yml
中定义了一个默认端口:my_role/defaults/main.yml
:YAML
--- app_port: 8080
你可以在多个地方尝试覆盖它:
在
my_role/vars/main.yml
中覆盖 (优先级高): YAML# my_role/vars/main.yml --- app_port: 9000
此时,执行角色时
app_port
将是9000
。在
inventory/group_vars/webservers.yml
中覆盖 (优先级更高): YAML# inventory/group_vars/webservers.yml --- app_port: 80
如果你的Playbook针对
webservers
组执行,那么app_port
将是80
。在 Playbook 中定义变量 (优先级更高): YAML
# site.yml --- - name: Deploy app hosts: webservers vars: app_port: 8888 # Playbook 中定义的变量 roles: - my_role
此时,
app_port
将是8888
。作为角色参数传递 (优先级更高): YAML
# site.yml --- - name: Deploy app hosts: webservers roles: - role: my_role app_port: 7777 # 作为角色参数传递
此时,
app_port
将是7777
。通过命令行
-e
或--extra-vars
强制覆盖 (最高优先级): 无论你的变量在Playbook、角色还是Inventory中如何定义,通过-e
或--extra-vars
传递的变量都将具有最高优先级,强制覆盖所有其他定义。 Bashansible-playbook site.yml -i inventory.ini -e "app_port=6666"
在这种情况下,
app_port
的值将是6666
。
总结:
要“强制”覆盖变量,你只需在优先级更高的位置重新定义它。而最高效、最可靠的“强制”方式通常是使用
--extra-vars
(-e
) 命令行选项,因为它在变量优先级链中处于最顶端。然而,在日常使用中,良好的变量组织(例如,在
defaults/
中定义默认值,在group_vars/
或host_vars/
中定义环境或主机特定值)通常是更推荐的做法,因为它使得配置更易于管理和理解,而不是过度依赖命令行强制覆盖。命令行选项通常用于临时测试或一次性运行。Ansible如何加密数据库密码
在 Ansible 项目中加密数据库密码(以及其他任何敏感数据)的最佳实践是使用 Ansible Vault。Ansible Vault 提供了一种强大的方式来加密文件或单个字符串,确保敏感信息(如密码、API 密钥、私钥等)在版本控制系统或共享环境中是安全的。
Ansible Vault 的工作原理
Ansible Vault 使用 AES256 对称加密。这意味着加密和解密都使用同一个密码(或密钥文件)。当你运行 Ansible Playbook 时,如果其中包含 Vault 加密的数据,你需要提供 Vault 密码才能解密并使用这些数据。
如何加密数据库密码
Ansible Vault 提供了两种主要方式来加密数据:
- 加密整个文件 (推荐用于变量文件)
- 加密单个字符串 (用于直接在 Playbook 或模板中嵌入少量敏感信息)
1. 加密整个变量文件 (推荐)
这是最常见且推荐的做法,因为它能让你将所有敏感变量集中管理在一个加密的文件中。
步骤:
a. 创建一个新的加密文件: 你可以创建一个新的文件来存放数据库密码,并直接将其加密。
Bash
ansible-vault create group_vars/all/vault.yml
运行此命令后,系统会提示你输入并确认一个 Vault 密码。然后,它会使用你配置的默认编辑器(通常是 Vim)打开一个空白文件。
在打开的编辑器中,以 YAML 格式输入你的数据库密码变量:
YAML
# group_vars/all/vault.yml --- db_username: myapp_user db_password: SuperSecretDBPassword123!
保存并关闭编辑器。文件
group_vars/all/vault.yml
将被加密。b. 加密一个已存在的未加密文件: 如果你已经有一个包含密码的变量文件,你可以加密它:
Bash
ansible-vault encrypt group_vars/all/db_credentials.yml
同样,系统会提示你输入并确认 Vault 密码。
c. 在 Playbook 中使用加密变量: 一旦文件被加密,你就可以像使用普通变量一样在 Playbook 中引用它:
YAML
# my_playbook.yml --- - name: Configure database connection hosts: databases become: yes vars_files: - group_vars/all/vault.yml # Ansible 会自动识别并尝试解密这个文件 tasks: - name: Create database user community.mysql.mysql_user: # 示例:使用 MySQL 模块 name: "{{ db_username }}" password: "{{ db_password }}" host: "%" state: present login_user: root login_password: "{{ root_db_password }}" # 如果连接 MySQL 需要 root 密码
当运行
ansible-playbook
时,你需要提供 Vault 密码:Bash
ansible-playbook my_playbook.yml --ask-vault-pass # 或者使用密码文件 (更推荐用于自动化): ansible-playbook my_playbook.yml --vault-password-file ~/.ansible/vault_pass.txt
2. 加密单个字符串
这种方法适用于你只需要加密一小段文本,并将其直接嵌入到 Playbook 或模板中。
步骤:
a. 加密字符串: 使用
ansible-vault encrypt_string
命令。Bash
ansible-vault encrypt_string --stdin-name 'db_password' # 提示: Reading plaintext input from stdin. (ctrl-d to end input) # 然后输入你的密码,例如:MyStrongDBPass! # 按 Ctrl+D (两次,如果密码没有换行) # 然后会输出加密后的字符串
输出可能类似这样:
YAML
db_password: !vault | $ANSIBLE_VAULT;1.1;AES256 6134626330363231363639356365313936663564613262333732386435383561333732653334643132666531 ... (更多加密内容) ...
b. 将加密字符串粘贴到变量文件中或 Playbook 中: 将整个输出(包括
db_password: !vault |
和其后的所有加密行)复制到你的变量文件(例如group_vars/all.yml
或host_vars/my_db_server.yml
)或 Playbook 的vars:
部分。加密的密码如何解密传递给Playbook
当你有一个使用 Ansible Vault 加密的数据库密码文件(例如
group_vars/all/vault.yml
或包含加密字符串的变量文件)时,你需要确保 Ansible 在执行 Playbook 时能够访问 Vault 密码,以便解密这些敏感数据。有几种方法可以将 Vault 密码传递给 Playbook,从交互式到自动化,以下是最常用的几种方式:
1. 交互式输入密码 (最简单,但适合手动执行)
这是最直接的方式。当你运行
ansible-playbook
命令时,添加--ask-vault-pass
或简写-k
。Ansible 会在运行时提示你输入 Vault 密码。Bash
ansible-playbook your_playbook.yml --ask-vault-pass
优点: 简单方便,不需要创建额外的文件。缺点: 每次运行都需要手动输入密码,不适合自动化脚本或 CI/CD。
2. 使用密码文件 (推荐用于自动化)
为了避免每次手动输入密码,你可以将 Vault 密码保存在一个文件中。强烈建议将此文件存放在安全的位置,并确保其权限受到严格限制,不要将其提交到版本控制系统(如 Git)!
a. 创建密码文件: 创建一个只包含你的 Vault 密码的文本文件。例如,在你的用户主目录下的
.ansible
隐藏目录中:Bash
mkdir -p ~/.ansible echo "YourStrongVaultPassword123" > ~/.ansible/vault_pass.txt chmod 600 ~/.ansible/vault_pass.txt # 限制文件权限,只有所有者可读写
b. 传递密码文件给 Playbook: 使用
--vault-password-file
或简写--vault-id
选项。Bash
ansible-playbook your_playbook.yml --vault-password-file ~/.ansible/vault_pass.txt # 或者使用 --vault-id (Ansible 2.4+ 推荐) ansible-playbook your_playbook.yml --vault-id @~/.ansible/vault_pass.txt
优点: 自动化友好,无需手动输入。缺点: 密码以明文形式存储在文件中,虽然权限受限,但仍需谨慎管理。
3. 使用
ANSIBLE_VAULT_PASSWORD_FILE
环境变量你可以设置一个环境变量来指向你的 Vault 密码文件。这样,你就无需在每个
ansible-playbook
命令中指定--vault-password-file
。Bash
export ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible/vault_pass.txt ansible-playbook your_playbook.yml
优点: 更简洁的命令,特别是在自动化脚本中。缺点: 环境变量通常只在当前会话中有效,或者需要配置在启动脚本中。
4. 使用 Vault ID 和密码脚本 (更灵活,推荐用于复杂场景和多 Vault 密码)
Ansible 2.4 引入了 Vault ID 功能,允许你使用多个 Vault 密码,每个密码用于不同的加密文件。这在处理不同环境(如开发、测试、生产)或不同团队的敏感数据时非常有用。你可以通过一个脚本来动态提供密码。
a. 定义 Vault ID: 在加密文件时,使用
--vault-id
选项来给文件关联一个 ID:Bash
ansible-vault create group_vars/dev/vault.yml --vault-id dev@prompt ansible-vault create group_vars/prod/vault.yml --vault-id prod@prompt
b. 创建一个 Vault 密码脚本: 这个脚本应该根据传入的 Vault ID 输出相应的密码。
~/.ansible/vault_pass_script.sh
:Bash
#!/bin/bash # 获取传入的 Vault ID VAULT_ID="$1" case "$VAULT_ID" in dev) echo "DevVaultPassword" ;; prod) echo "ProdVaultPassword" ;; *) echo "DefaultVaultPassword" # 默认密码或错误处理 ;; esac
注意: 同样,请确保此脚本安全,不要在其中硬编码生产密码并提交到 Git。 理想情况下,脚本应该从安全的外部源(如 HashiCorp Vault、AWS Secrets Manager 等)获取密码。
给脚本执行权限:
Bash
chmod +x ~/.ansible/vault_pass_script.sh
c. 运行 Playbook: 使用
--vault-id
选项指定脚本:Bash
ansible-playbook your_playbook.yml --vault-id dev@~/.ansible/vault_pass_script.sh # 或 ansible-playbook your_playbook.yml --vault-id prod@~/.ansible/vault_pass_script.sh
优点: 支持多个 Vault 密码,可以与外部秘密管理系统集成,非常灵活。缺点: 配置相对复杂一些。
5. 在
ansible.cfg
中配置 Vault 密码文件你也可以在 Ansible 的配置文件
ansible.cfg
中全局指定 Vault 密码文件。这使得你无需在命令行中每次都输入选项。ansible.cfg
(项目根目录或全局配置):Ini, TOML
[defaults] vault_password_file = ~/.ansible/vault_pass.txt # 或者如果你使用 Vault ID: # vault_password_file = ~/.ansible/vault_pass_script.sh
优点: 最简洁的命令行,只需运行
ansible-playbook your_playbook.yml
。缺点: 密码文件路径是硬编码的,可能不适合多用户或多环境场景。如果使用脚本,脚本本身的安全管理仍是关键。最佳实践和安全性考虑
- 不要将 Vault 密码本身提交到版本控制系统 (Git)。 无论是明文密码文件还是包含密码的脚本。
- 严格限制 Vault 密码文件的权限。
chmod 600
是一个好的开始。 - 考虑使用外部秘密管理系统。 对于生产环境和团队协作,Ansible Vault 只是加密了数据,但Vault 密码本身仍需要管理。集成专业的秘密管理工具(如 HashiCorp Vault、AWS Secrets Manager、Azure Key Vault、Google Secret Manager)可以更好地管理和分发 Vault 密码。Ansible 可以配置为通过脚本从这些系统中动态获取 Vault 密码。
- 定期轮换 Vault 密码。
- 为不同的环境(开发、测试、生产)使用不同的 Vault 密码。
梳理芋道项目部署的步骤和角色
你觉得整个项目分为几个角色
项目角色划分
在“芋道”项目(或任何类似复杂项目)的部署过程中,通常会涉及以下几个核心角色:
1. 开发人员 (Developer)
职责:
- 编写、测试项目代码,确保代码质量和功能实现。
- 提供最新的、可部署的代码版本。
- 协助排查与代码逻辑相关的部署问题。
- 提供项目的构建、打包指南。
所需技能: 熟练掌握 Java/Spring Boot/Vue.js/Element UI 等项目技术栈,理解项目架构。
2. 运维工程师 (Operations Engineer / DevOps Engineer)
职责:
- 环境准备: 负责准备和配置部署所需的基础设施环境(服务器、网络、操作系统等)。
- 依赖安装与配置: 安装和配置 Java、数据库、Redis、Nginx 等所有运行时依赖。
- 部署执行: 负责将开发人员提供的构建产物部署到目标服务器,并启动服务。
- 自动化: 编写自动化脚本(如 Ansible Playbook、Docker Compose 文件)或配置 CI/CD 流水线,实现部署的自动化。
- 监控与报警: 配置和维护监控系统,确保服务高可用和性能稳定。
- 故障排查: 负责解决部署、运行过程中遇到的环境、基础设施或配置问题。
- 安全管理: 实施服务器和应用的安全策略。
所需技能: 熟练掌握 Linux 命令、网络知识、Shell 脚本、Ansible/Docker/Kubernetes 等部署工具、监控工具(如 Prometheus、Grafana)。
3. 数据库管理员 (DBA - Database Administrator)
职责:
- 数据库安装与配置: 负责数据库软件(MySQL/PostgreSQL 等)的安装、优化和安全配置。
- 数据库初始化: 执行项目所需的 SQL 脚本,创建数据库、表结构、索引等。
- 数据备份与恢复: 制定和执行数据库备份策略,确保数据安全。
- 性能优化与故障排查: 监控数据库性能,进行优化,并解决数据库相关的故障。
所需技能: 深入了解所用数据库的体系结构、SQL 语言、性能调优、备份恢复等。
4. 项目经理 / 技术负责人 (Project Manager / Tech Lead)
职责:
- 协调沟通: 协调开发、运维、DBA 之间的工作,确保部署流程顺畅。
- 进度管理: 跟踪部署进度,识别和解决潜在风险。
- 决策支持: 在部署过程中遇到重大技术选型或问题时提供决策支持。
- 需求理解: 确保部署结果符合项目需求和预期。
所需技能: 项目管理、沟通协调能力、对项目技术栈和业务的宏观理解。
按顺序描述每个角色要进行的步骤(task),比如
拉取前端代码
安装前端环境
构建前端项目
………………
项目角色与对应的部署任务 (Tasks)
1. 开发人员 (Developer)
职责: 提供可部署的代码和构建指导。
Task 1: 完成代码开发与自测
- 确保所有功能按设计实现并通过单元测试和集成测试。
- 修复已知的缺陷。
Task 2: 更新项目文档与构建脚本
- 更新
README.md
,包括最新的环境要求、依赖项、构建命令。 - 确保
pom.xml
(Maven) 和package.json
(npm/yarn) 配置正确,依赖清晰。 - (如果需要)提供数据库初始化 SQL 脚本的最新版本。
- 更新
Task 3: 将代码推送到版本控制系统
- 将后端(Spring Boot/Cloud)和前端(Vue/Element UI)的最新稳定代码推送到 Git 仓库的指定分支(如
main
或release
分支)。
- 将后端(Spring Boot/Cloud)和前端(Vue/Element UI)的最新稳定代码推送到 Git 仓库的指定分支(如
Task 4: 提供构建产物路径/指导
- 明确告知运维人员后端 JAR 包(或 WAR 包)和前端静态文件(
dist
目录)的生成路径。 - 提供任何特殊的构建参数或环境变量设置。
- 明确告知运维人员后端 JAR 包(或 WAR 包)和前端静态文件(
2. 数据库管理员 (DBA - Database Administrator)
职责: 负责数据库的准备、初始化和维护。
Task 1: 安装和配置数据库系统
- 根据项目需求(如 MySQL 8.0),在指定服务器上安装数据库软件。
- 进行数据库基础配置(如字符集、连接数、缓存设置等)。
- 配置数据库监听端口,确保外部可访问性。
Task 2: 创建数据库实例和用户
- 创建“芋道”项目专用的数据库(如
yudao
)。 - 创建数据库用户(如
yudao_user
),并授予仅所需权限(例如,GRANT ALL PRIVILEGES ON yudao.* TO 'yudao_user'@'%' IDENTIFIED BY 'your_db_password';
)。
- 创建“芋道”项目专用的数据库(如
Task 3: 执行数据库初始化脚本
- 获取开发人员提供的最新 SQL 脚本。
- 在创建的数据库实例上执行这些脚本,初始化表结构和基础数据。
mysql -u yudao_user -p yudao < yudao_schema.sql
mysql -u yudao_user -p yudao < yudao_data.sql
(如果数据和结构分离)
Task 4: 配置数据库备份策略
- 根据业务需求,制定并实现数据库定期全量/增量备份计划。
Task 5: 提供数据库连接信息给运维
- 将数据库服务器地址、端口、数据库名、用户名、密码等连接信息安全地传递给运维工程师。
3. 运维工程师 (Operations Engineer / DevOps Engineer)
职责: 负责整个部署环境的搭建、项目构建、部署、启动、监控和自动化。
Task 1: 准备服务器环境
- 采购/申请服务器: 获取所需的物理机、虚拟机或云服务器实例。
- 安装操作系统: 完成 Linux 操作系统的安装(如 CentOS/Ubuntu),并进行基础配置(如网络、主机名、SSH 密钥登录等)。
- 更新系统和安装常用工具:
sudo apt update && sudo apt upgrade -y
或sudo yum update -y
。安装wget
,curl
,git
,unzip
,vim
等。 - 配置防火墙: 开放必要的端口(SSH, HTTP/HTTPS, 数据库、Redis、后端服务端口等)。
Task 2: 安装核心运行时依赖
安装 Java Development Kit (JDK): 根据项目要求安装特定版本的 JDK (如 OpenJDK 8 或 11)。
sudo apt install openjdk-8-jdk
或sudo yum install java-1.8.0-openjdk-devel
安装 Maven: 用于后端项目构建。
sudo apt install maven
或 下载解压并配置环境变量。
安装 Node.js 和 npm/yarn: 用于前端项目构建。
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
或curl -sL https://rpm.nodesource.com/setup_16.x | sudo bash -
sudo apt install -y nodejs
或sudo yum install -y nodejs
npm install -g yarn
(可选)
安装 Redis:
sudo apt install redis-server
或sudo yum install redis
- 配置 Redis 持久化和密码保护。
安装 Nginx (或 Caddy):
sudo apt install nginx
或sudo yum install nginx
Task 3: 获取项目代码
- 在服务器上选择一个合适的工作目录(如
/opt/yudao
)。 git clone <repo_url> /opt/yudao
- 在服务器上选择一个合适的工作目录(如
Task 4: 配置后端项目
进入后端项目的
src/main/resources
目录(或根据项目结构),修改application.yml
或bootstrap.yml
。配置数据库连接信息: 根据 DBA 提供的连接参数修改。YAML
spring: datasource: url: jdbc:mysql://DB_IP:3306/yudao?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false username: yudao_user password: your_db_password
配置 Redis 连接信息: YAML
spring: redis: host: 127.0.0.1 port: 6379 password: your_redis_password # 如果设置了密码
配置其他服务地址: 如果项目有内部调用的微服务(如认证中心、网关),配置其地址。
Task 5: 配置前端项目
进入前端项目目录(如
yudao-ui-admin
)。修改环境变量文件(如
.env.production
或src/settings.js
),配置后端 API 的地址。JavaScript// 例如在 .env.production 或 settings.js 中 VUE_APP_BASE_API = 'http://your_domain_or_ip/api'; // Nginx 代理的后端 API 地址
Task 6: 构建后端项目
- 进入后端项目根目录(
/opt/yudao/backend
)。 - 执行 Maven 构建命令:
mvn clean package -DskipTests
- 验证产物: 检查每个子模块的
target
目录下是否生成了.jar
包。
- 进入后端项目根目录(
Task 7: 构建前端项目
- 进入前端项目根目录(
/opt/yudao/frontend
)。 - 安装前端依赖:
npm install
或yarn install
- 构建生产版本:
npm run build
或yarn build
- 验证产物: 检查前端项目目录下是否生成了
dist
文件夹,其中包含编译后的静态文件。
- 进入前端项目根目录(
Task 8: 部署后端服务
创建后端服务部署目录(如
/opt/yudao/apps
)。将构建好的 JAR 包拷贝到对应目录:
cp /opt/yudao/backend/yudao-XXX-biz/target/yudao-XXX-biz.jar /opt/yudao/apps/
- 对每个 JAR 包重复此操作。
编写 Systemd Unit 文件(推荐): 为每个后端服务创建
systemd
服务文件,实现开机自启、进程守护和日志管理。- 例如:
/etc/systemd/system/yudao-server.service
Ini, TOML
[Unit] Description=YuDao Backend Service After=network.target [Service] User=yudao_user # 建议创建单独的用户运行服务 ExecStart=/usr/bin/java -jar /opt/yudao/apps/yudao-server.jar --spring.profiles.active=prod SuccessExitStatus=143 Restart=always RestartSec=5 LimitNOFILE=65536 StandardOutput=file:/var/log/yudao/server.log StandardError=file:/var/log/yudao/server-error.log [Install] WantedBy=multi-user.target
- 例如:
启动后端服务:
sudo systemctl daemon-reload
sudo systemctl enable yudao-server.service
(开机自启)sudo systemctl start yudao-server.service
检查服务状态:
sudo systemctl status yudao-server.service
或ps -ef | grep java
。检查日志:
tail -f /var/log/yudao/server.log
。
Task 9: 部署前端静态文件
创建 Nginx 静态文件根目录(如
/var/www/yudao-admin
)。将前端
dist
目录下的所有内容拷贝到该目录:cp -r /opt/yudao/frontend/dist/* /var/www/yudao-admin/
Task 10: 配置 Nginx 反向代理
编辑 Nginx 配置文件(如
/etc/nginx/sites-available/yudao.conf
),并创建软链接到sites-enabled
。配置 HTTP 和 HTTPS (如果使用) 监听。
配置前端静态文件服务: Nginx
server { listen 80; server_name your_domain_or_ip; location / { root /var/www/yudao-admin; index index.html; try_files $uri $uri/ /index.html; # 解决 Vue Router history 模式刷新问题 } # ... 其他配置 ... }
配置后端 API 转发: Nginx
server { # ... 其他配置 ... location /api/ { proxy_pass http://localhost:8080/; # 假设后端服务监听 8080 端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # ... 其他配置 ... }
加载 Nginx 配置:
sudo nginx -t
(检查配置语法)sudo nginx -s reload
(重新加载配置)
Task 11: 进行初步验证
- 通过浏览器访问项目域名或 IP,检查前端页面是否正常加载。
- 尝试登录系统,验证前端与后端 API 交互是否正常。
- 检查后端服务日志,确保没有致命错误。
Task 12: 配置监控与日志
- 设置服务器资源监控(CPU、内存、磁盘、网络)。
- 配置应用程序日志收集(如使用
logrotate
管理日志文件大小,或集成 ELK Stack/Loki)。 - 如果集成,配置 Prometheus/Grafana 监控指标和报警规则。
Task 13: 实施安全加固
- 确保所有服务的用户权限最小化。
- 定期检查系统和软件的安全补丁。
- 强化 SSH 登录安全(禁用密码登录,只允许密钥)。
- 审查防火墙规则。
Task 14: 自动化部署(可选,但推荐)
- 将以上部署步骤编写成 Ansible Playbook 或 Docker Compose 文件。
- 配置 CI/CD 流水线,实现代码提交后自动构建和部署。
4. 项目经理 / 技术负责人 (Project Manager / Tech Lead)
职责: 协调、决策和确保项目顺利交付。
Task 1: 确认部署计划
- 与开发、运维、DBA 沟通,确认部署范围、时间表和资源需求。
- 评审技术方案和部署策略。
Task 2: 协调各方工作
- 定期组织会议,跟踪部署进度。
- 及时解决各角色之间的依赖和阻塞问题。
- 确保开发人员及时提供最新代码和文档,DBA 提供数据库支持,运维人员按计划执行部署。
Task 3: 风险管理与决策
- 识别部署过程中可能出现的风险(如环境不兼容、性能瓶颈、安全漏洞)。
- 当出现重大问题时,评估影响并做出决策(如是否回滚、如何调整策略)。
Task 4: 最终验收
- 在部署完成后,与 QA 或业务方一起进行最终功能和性能验证,确保系统满足上线标准。
Task 5: 沟通与汇报
- 向相关干系人(如领导、客户)汇报部署进展和结果。
这些步骤你觉得应该如何拆分成多个task
Ansible Playbook 任务拆分策略
一个完整的“芋道”项目部署通常会包含多个 Playbook,每个 Playbook 负责一个特定的阶段或一组相关的操作。这有助于:
- 模块化: 每个 Playbook 聚焦于一个明确的目标,方便理解和调试。
- 可重用性: 某些公共任务(如环境准备)可以作为单独的 Playbook 或 Role 在不同项目中复用。
- 职责分离: 对应部署流程的不同阶段,易于团队协作。
- 增量部署: 可以选择性地运行某个阶段的 Playbook,比如只更新前端或只重启某个后端服务。
Playbook 拆分建议及对应的任务示例
我会按照部署的逻辑顺序,将任务分组到不同的 Playbook 中。
1.
00-prepare-environment.yml
:环境准备与依赖安装这个 Playbook 专注于服务器的基础环境搭建和所有必要软件的安装。通常由运维工程师主导。
- 目标: 确保服务器具备运行“芋道”项目的所有基础条件。
- 角色: 运维工程师。
- 主要任务类型: 包管理、文件管理、服务管理。
YAML
--- # playbook: 00-prepare-environment.yml - name: 准备服务器环境和安装依赖 hosts: your_servers_group # 定义你的服务器组,例如 [web_servers] 和 [app_servers] become: yes # 需要root权限执行安装 tasks: - name: 更新系统软件包 ansible.builtin.apt: update_cache: yes upgrade: 'yes' when: ansible_os_family == "Debian" # 或 yum 模块 for RedHat/CentOS - name: 安装常用系统工具 ansible.builtin.package: name: "{{ item }}" state: present loop: - wget - curl - git - unzip - vim - build-essential # 对于某些编译需求 - name: 安装 Java Development Kit (JDK) ansible.builtin.apt: # 或 yum name: openjdk-11-jdk # 根据项目需求调整版本 state: present - name: 安装 Maven ansible.builtin.apt: # 或 yum name: maven state: present - name: 安装 Node.js 和 npm ansible.builtin.shell: | curl -sL https://deb.nodesource.com/setup_16.x | bash - # 根据Node.js版本调整 apt-get install -y nodejs args: creates: /usr/bin/node # 检查文件是否存在以实现幂等性 when: ansible_os_family == "Debian" # 或使用 nvm 模块进行更灵活的Node.js版本管理 - name: 安装 Redis Server ansible.builtin.apt: # 或 yum name: redis-server state: present - name: 配置 Redis (例如设置密码和持久化) ansible.builtin.lineinfile: path: /etc/redis/redis.conf regexp: '^# requirepass foobared' line: 'requirepass your_redis_password' backrefs: yes notify: Restart Redis # 添加其他Redis配置,如 bind, protected-mode 等 - name: 安装 Nginx ansible.builtin.apt: # 或 yum name: nginx state: present notify: Start Nginx handlers: - name: Restart Redis ansible.builtin.service: name: redis-server state: restarted - name: Start Nginx ansible.builtin.service: name: nginx state: started enabled: yes
2.
01-setup-database.yml
:数据库初始化这个 Playbook 负责数据库的创建、用户授权和初始脚本执行。通常需要 DBA 的支持,或者由运维人员在获得授权后执行。
- 目标: 初始化“芋道”项目所需的数据库。
- 角色: DBA / 运维工程师。
- 主要任务类型: 数据库操作。
YAML
--- # playbook: 01-setup-database.yml - name: 初始化数据库 hosts: db_servers # 数据库服务器组 become: yes vars: db_name: yudao db_user: yudao_user db_password: your_db_password_safe sql_script_path: /opt/yudao/sql/init.sql # 假设SQL脚本已在服务器上 tasks: - name: 安装 MySQL 客户端 (如果需要在数据库服务器上执行脚本) ansible.builtin.apt: # 或 yum name: mysql-client state: present - name: 创建数据库 "{{ db_name }}" community.mysql.mysql_db: name: "{{ db_name }}" state: present login_user: root # 或者有权限创建DB的用户 login_password: "{{ mysql_root_password | default('') }}" # 从Vault获取密码 # 如果没有community.mysql,可以使用 shell 模块执行 mysql 命令 - name: 创建数据库用户 "{{ db_user }}" 并授权 community.mysql.mysql_user: name: "{{ db_user }}" password: "{{ db_password }}" priv: "{{ db_name }}.*:ALL" host: "%" # 允许从任何主机连接,生产环境应限制为特定IP state: present login_user: root login_password: "{{ mysql_root_password | default('') }}" - name: 执行数据库初始化脚本 community.mysql.mysql_db: name: "{{ db_name }}" state: import target: "{{ sql_script_path }}" login_user: "{{ db_user }}" login_password: "{{ db_password }}" # 确保 sql_script_path 在目标服务器上存在,可以通过 copy 模块先上传
3.
02-deploy-backend.yml
:后端服务部署这个 Playbook 负责后端代码的获取、配置、构建、部署和启动。
- 目标: 部署并启动所有后端服务。
- 角色: 运维工程师。
- 主要任务类型: Git操作、文件模板、Java构建、服务管理。
YAML
--- # playbook: 02-deploy-backend.yml - name: 部署后端服务 hosts: app_servers # 应用程序服务器组 become: yes vars: app_root: /opt/yudao backend_repo_url: "https://gitee.com/yudaocode/yudao.git" # 替换为你的后端仓库 backend_project_dir: "{{ app_root }}/yudao-server" # 仓库内的后端项目路径 # 后端服务列表,包含名称和其在构建产物中的JAR包路径 backend_services: - name: yudao-server-biz jar_path: "yudao-server/yudao-module-system/yudao-module-system-biz/target/yudao-module-system-biz.jar" # 添加其他后端服务,例如 auth, gateway 等 tasks: - name: 创建应用程序根目录 ansible.builtin.file: path: "{{ app_root }}" state: directory mode: '0755' - name: 拉取或更新后端代码 ansible.builtin.git: repo: "{{ backend_repo_url }}" dest: "{{ app_root }}" version: main # 或指定分支/tag force: yes # 强制更新,注意生产环境慎用 - name: 配置后端应用属性文件 (application.yml) ansible.builtin.template: src: templates/application.yml.j2 # 定义模板文件,包含数据库、Redis等配置 dest: "{{ app_root }}/{{ backend_project_dir }}/src/main/resources/application.yml" # 确保路径正确 mode: '0644' # application.yml.j2 示例内容: # spring: # datasource: # url: jdbc:mysql://{{ groups['db_servers'][0] }}:3306/{{ db_name }}?... # username: {{ db_user }} # password: "{{ db_password_from_vault }}" # 从Ansible Vault获取 - name: 构建后端项目 ansible.builtin.shell: "mvn clean package -DskipTests" args: chdir: "{{ app_root }}/{{ backend_project_dir }}" environment: JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64 # 确保指向正确的JDK路径 - name: 部署后端JAR包并创建服务目录 ansible.builtin.copy: src: "{{ app_root }}/{{ item.jar_path }}" dest: "/opt/yudao/apps/{{ item.name }}.jar" mode: '0644' loop: "{{ backend_services }}" # 当jar_path是相对于repo根目录时,使用 join() 拼接: # src: "{{ app_root }}/{{ item.jar_path }}" - name: 配置 Systemd 服务文件 ansible.builtin.template: src: templates/yudao-service.service.j2 # 通用模板 dest: "/etc/systemd/system/{{ item.name }}.service" mode: '0644' loop: "{{ backend_services }}" notify: Restart Backend Services - name: 启用并启动后端服务 ansible.builtin.systemd: name: "{{ item.name }}.service" state: started enabled: yes daemon_reload: yes loop: "{{ backend_services }}" handlers: - name: Restart Backend Services ansible.builtin.systemd: name: "{{ item.name }}.service" state: restarted loop: "{{ backend_services }}" # 确保这里的 loop 变量名与 tasks 中的一致
4.
03-deploy-frontend.yml
:前端服务部署这个 Playbook 负责前端代码的获取、配置、构建和部署到 Nginx。
- 目标: 部署并配置前端静态资源。
- 角色: 运维工程师。
- 主要任务类型: Git操作、Node.js构建、文件管理、Nginx配置。
YAML
--- # playbook: 03-deploy-frontend.yml - name: 部署前端服务 hosts: web_servers # Web服务器组(通常与后端服务器相同或不同) become: yes vars: app_root: /opt/yudao frontend_repo_url: "https://gitee.com/yudaocode/yudao.git" # 替换为你的前端仓库 frontend_project_dir: "{{ app_root }}/yudao-ui-admin" # 仓库内的前端项目路径 nginx_frontend_root: "/var/www/yudao-admin" backend_api_url: "http://localhost:8080/api" # 后端API的Nginx内部代理地址 tasks: - name: 拉取或更新前端代码 ansible.builtin.git: repo: "{{ frontend_repo_url }}" dest: "{{ app_root }}" version: main force: yes - name: 安装前端项目依赖 ansible.builtin.shell: "npm install --force" # --force 可以处理peerDependencies警告 args: chdir: "{{ app_root }}/{{ frontend_project_dir }}" environment: NODE_OPTIONS: "--max_old_space_size=4096" # 解决内存溢出问题 - name: 配置前端API环境变量 (例如 .env.production) ansible.builtin.lineinfile: path: "{{ app_root }}/{{ frontend_project_dir }}/.env.production" regexp: '^VITE_APP_BASE_API=' # 根据实际前端框架的变量名调整 line: 'VITE_APP_BASE_API="{{ backend_api_url }}"' # Vue 3 Vite 项目 create: yes state: present # 对于Vue CLI项目可能是 VUE_APP_BASE_API - name: 构建前端项目 ansible.builtin.shell: "npm run build:prod" # 根据 package.json 脚本调整 args: chdir: "{{ app_root }}/{{ frontend_project_dir }}" environment: NODE_ENV: production # 确保是生产模式构建 - name: 清理Nginx前端根目录 (可选,确保部署干净) ansible.builtin.file: path: "{{ nginx_frontend_root }}" state: absent when: delete_old_frontend_files | default(true) # 增加控制变量 - name: 创建Nginx前端根目录 ansible.builtin.file: path: "{{ nginx_frontend_root }}" state: directory mode: '0755' - name: 拷贝前端构建产物到Nginx根目录 ansible.builtin.copy: src: "{{ app_root }}/{{ frontend_project_dir }}/dist/" # 假设构建产物在dist目录 dest: "{{ nginx_frontend_root }}" remote_src: yes # 表示源文件在远程服务器上 - name: 配置 Nginx 反向代理和静态服务 ansible.builtin.template: src: templates/nginx.conf.j2 dest: /etc/nginx/conf.d/yudao.conf # 或 sites-available/sites-enabled mode: '0644' notify: Reload Nginx handlers: - name: Reload Nginx ansible.builtin.service: name: nginx state: reloaded
5.
04-monitoring-and-logging.yml
:监控与日志配置这个 Playbook 负责配置日志收集和基础监控。
- 目标: 确保应用日志被正确管理和监控。
- 角色: 运维工程师。
- 主要任务类型: 文件管理、服务管理、包管理。
YAML
--- # playbook: 04-monitoring-and-logging.yml - name: 配置监控和日志 hosts: all_servers # 或特定服务器组 become: yes tasks: - name: 创建日志目录 ansible.builtin.file: path: /var/log/yudao state: directory mode: '0755' - name: 配置 logrotate (例如为后端服务日志) ansible.builtin.template: src: templates/logrotate-yudao.conf.j2 dest: /etc/logrotate.d/yudao mode: '0644' # 以下是集成监控工具的示例,具体取决于你使用的工具 - name: 安装 Prometheus Node Exporter (用于主机监控) ansible.builtin.unarchive: src: https://github.com/prometheus/node_exporter/releases/download/v1.3.1/node_exporter-1.3.1.linux-amd64.tar.gz # 替换为最新版本 dest: /usr/local/bin/ remote_src: yes extra_opts: [--strip-components=1] # ... 然后创建systemd服务启动exporter
如何组织这些 Playbook?
你可以创建一个主入口 Playbook (
main-deploy.yml
) 来按顺序调用这些子 Playbook,或者直接按需执行它们。示例
main-deploy.yml
:YAML
--- - import_playbook: 00-prepare-environment.yml - import_playbook: 01-setup-database.yml - import_playbook: 02-deploy-backend.yml - import_playbook: 03-deploy-frontend.yml - import_playbook: 04-monitoring-and-logging.yml
目录结构建议:
yudao-ansible/ ├── group_vars/ │ ├── all/ │ │ └── vault.yml # 加密敏感数据,如数据库密码、Redis密码 │ │ └── common_vars.yml # 非敏感的通用变量 │ └── app_servers.yml # 应用服务器特有变量 │ └── db_servers.yml # 数据库服务器特有变量 │ └── web_servers.yml # Web服务器特有变量 ├── inventories/ │ └── production # 生产环境主机清单 │ └── development # 开发环境主机清单 ├── templates/ │ ├── application.yml.j2 │ ├── nginx.conf.j2 │ ├── yudao-service.service.j2 │ └── logrotate-yudao.conf.j2 ├── sql/ # 存放项目初始化SQL脚本 │ └── init.sql ├── 00-prepare-environment.yml ├── 01-setup-database.yml ├── 02-deploy-backend.yml ├── 03-deploy-frontend.yml ├── 04-monitoring-and-logging.yml └── main-deploy.yml
运行 Playbook
使用
ansible-playbook
命令运行你的 Playbook。请记住使用--vault-password-file
或--ask-vault-pass
来解密vault.yml
中的敏感信息。Bash
# 运行所有部署步骤 ansible-playbook -i inventories/production main-deploy.yml --vault-password-file ~/.ansible/vault_pass.txt # 只更新前端 ansible-playbook -i inventories/production 03-deploy-frontend.yml --vault-password-file ~/.ansible/vault_pass.txt