首先update一下

sudo apt update

然后可以通过apt安装ansible

sudo apt install ansible -y

验证安装

ansible --version

生成ssh 公钥

ssh-keygen -t rsa

把公钥发送给被控制的节点

ssh-copy-id sztu@10.11.153.173

可以看到公钥已经添加完成

可以开始编写Inventory清单了

sudo nano inventory

进行连通性测试
ansible -i inventory my_servers -m ping

写好playbook文件

\-\--

\- hosts: my_servers \# 在 Inventory
文件中定义此主机组,包含所有目标服务器

become: yes \# 以 root 权限执行任务

vars:

\# \-\-- 项目部署配置 \-\--

project_base_dir_on_target: /silengensis \# 项目在目标机上的父目录

project_name: file \# 当前部署的项目名称

project_source_on_control_node: \"/home/sztu/ansible/ansible/{{
project_name }}/\" \# 项目文件在 Ansible 控制节点上的路径

\# \-\-- 派生变量 (通常无需手动修改) \-\--

project_path_on_target: \"{{ project_base_dir_on_target }}/{{
project_name }}\" \# 目标机上项目的完整路径

compose_log_file_on_target: \"/tmp/{{ project_name }}\_compose_up\_{{
ansible_date_time.iso8601_basic_short }}.log\" \# Docker Compose
日志文件名 (带时间戳)

\# \-\-- Docker 环境配置 \-\--

docker_gpg_key_url:
https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg \# Docker GPG
密钥源

docker_apt_repo_template: \"deb \[arch=amd64\]
https://mirrors.aliyun.com/docker-ce/linux/ubuntu {{
ansible_lsb.codename }} stable\" \# Docker APT 仓库源

docker_daemon_json_content: \| \# Docker 镜像加速器配置

{

\"registry-mirrors\": \[

\"https://docker.mirrors.ustc.edu.cn\",

\"https://docker.m.daocloud.io\",

\"https://hub-mirror.c.163.com\",

\"https://registry.docker-cn.com\"

\]

}

\# \-\-- GPUSstack 服务配置 (差异化配置建议在 Inventory 中定义) \-\--

\# gpustack_install_port: 8080 \# GPUSstack
服务端口,如此处定义则为全局默认值

\# 如果希望每台机器不同,请在 Inventory 中为对应主机设置此变量

\# \-\-- GPUSstack 安装脚本配置 \-\--

gpustack_binary_path: /usr/local/bin/gpustack \# GPUSstack
安装后的可执行文件路径 (用于 \'creates\' 判断)

\# \-\-- PyPI 镜像配置 (用于 GPUSstack 安装脚本) \-\--

\# 为了加速 Python 包的下载,推荐使用国内 PyPI 镜像

temp_pip_index_url: \"https://pypi.tuna.tsinghua.edu.cn/simple\"

temp_pip_trusted_host: \"pypi.tuna.tsinghua.edu.cn\"

\# \-\-- DNS 临时配置 (如果目标机 DNS 有问题,可临时指定) \-\--

primary_dns_server: \"119.29.29.29\"

secondary_dns_server: \"182.254.116.116\"

\# network_interface_for_dns: \"ens3\" \#
目标机主网络接口名,如此处定义则为全局默认值

\# 如果不同服务器接口名不同,请在 Inventory 中设置

\# \-\-- 异步任务参数配置 \-\--

\# Docker Compose 异步参数

async_max_runtime_seconds: 3600 \# 异步任务允许的最长运行时间 (秒)

async_poll_retries: 180 \# 监控异步任务的最大轮询次数

async_poll_delay_seconds: 10 \# 每次轮询的间隔时间 (秒)

\# GPUSstack 安装异步参数 (通常安装时间较长)

gpustack_async_max_runtime_seconds: 10800 \# (3 小时)

gpustack_async_poll_retries: 540

gpustack_async_poll_delay_seconds: 20

handlers:

\- name: 重启并启用 Docker 服务 \# Handler 名称

ansible.builtin.systemd:

name: docker

state: restarted \# 确保服务重启

enabled: yes \# 确保服务开机自启

listen: \"restart_docker_service\" \# 监听的事件名

tasks:

\# \-\-- 阶段零:准备工作 - 临时设置 DNS (如果需要) \-\--

\- name: DNS \| 临时设置 DNS 服务器以优化网络访问

ansible.builtin.command: \"resolvectl dns {{ network_interface_for_dns
\| default(\'ens3\') }} {{ primary_dns_server }} {{ secondary_dns_server
}}\"

\# 使用 default(\'ens3\') 提供一个默认接口名,但强烈建议在 Inventory
中为每台机器正确设置 network_interface_for_dns

register: dns_set_result

changed_when: true \# 假设此操作总是尝试应用,即使 DNS 已是目标值

tags:

\- dns_setup \# 标签,方便按标签运行或跳过任务

\- always \# 表示此任务应尽可能运行

\- name: DNS \| (可选) 验证临时 DNS 设置是否生效

block:

\- name: \"获取当前网络接口的 DNS 设置\"

ansible.builtin.command: \"resolvectl dns {{ network_interface_for_dns
\| default(\'ens3\') }}\"

register: new_dns_status

changed_when: false \# 仅读取信息,不改变状态

\- name: \"断言:检查主要 DNS 是否已按预期设置\"

ansible.builtin.assert:

that:

\- \"primary_dns_server in new_dns_status.stdout\" \#
验证输出中是否包含设置的主 DNS

fail_msg: \"DNS 未能成功设置为 {{ primary_dns_server }} on {{
network_interface_for_dns \| default(\'ens3\') }}. 当前 DNS: {{
new_dns_status.stdout_lines }}\"

success_msg: \"DNS 已成功设置为 {{ primary_dns_server }} on {{
network_interface_for_dns \| default(\'ens3\') }}.\"

\- name: \"调试:显示设置后的 DNS 信息\"

ansible.builtin.debug:

msg: \"当前接口 {{ network_interface_for_dns \| default(\'ens3\') }} 的
DNS 设置为: {{ new_dns_status.stdout_lines }}\"

when: dns_set_result.changed \# 仅在 DNS 设置任务报告更改后执行此验证块

tags:

\- dns_setup

\- always

\# \-\-- 阶段一:安装和配置 Docker \-\--

\- name: Docker \| 确保 Docker 环境已就绪

block:

\- name: \"1.1 系统更新与基础依赖安装\"

ansible.builtin.apt: { name: \[\"apt-transport-https\",
\"ca-certificates\", \"curl\", \"gnupg\", \"lsb-release\"\], state:
present, update_cache: yes }

\- name: \"1.2 添加 Docker 官方 GPG 密钥 (使用阿里云镜像)\"

ansible.builtin.apt_key: { url: \"{{ docker_gpg_key_url }}\", state:
present }

\- name: \"1.3 添加 Docker 官方 APT 软件源 (使用阿里云镜像)\"

ansible.builtin.apt_repository: { repo: \"{{ docker_apt_repo_template
}}\", state: present }

\- name: \"1.4 安装 Docker 相关软件包\"

ansible.builtin.apt: { name: \[\"docker-ce\", \"docker-ce-cli\",
\"containerd.io\", \"docker-compose-plugin\"\], state: present,
update_cache: yes }

\- name: \"1.5 配置 Docker 守护进程 (如设置镜像加速器)\"

ansible.builtin.copy: { dest: /etc/docker/daemon.json, content: \"{{
docker_daemon_json_content }}\", owner: root, group: root, mode:
\'0644\' }

notify: \"restart_docker_service\" \# 若此文件变更,则触发 Handler 重启
Docker

\- name: \"1.6 确保 Docker 服务运行并开机自启\"

ansible.builtin.systemd: { name: docker, state: started, enabled: yes }

tags:

\- docker_setup

\# \-\-- 阶段二:部署项目并使用 Docker Compose 启动 \-\--

\- name: 项目部署 \| 复制项目文件并启动 Docker Compose 服务

block:

\- name: \"2.1 创建项目部署目录\"

ansible.builtin.file: { path: \"{{ project_path_on_target }}\", state:
directory, mode: \'0755\', owner: root, group: root }

\- name: \"2.2 复制项目文件到目标主机\"

ansible.builtin.copy: { src: \"{{ project_source_on_control_node }}\",
dest: \"{{ project_path_on_target }}/\", mode: \'0644\' }

\- name: \"\[Async\] 2.3 后台启动 Docker Compose 并记录日志\"

ansible.builtin.shell:

cmd: \"docker compose up -d \> {{ compose_log_file_on_target }} 2\>&1\"

chdir: \"{{ project_path_on_target }}\"

async: \"{{ async_max_runtime_seconds }}\"

poll: 0 \# "即发即忘",Ansible 不等待此任务完成

register: compose_async_start

\- name: \"\[Monitor\] 2.4 监控 Docker Compose 启动完成状态\"

ansible.builtin.async_status: { jid: \"{{
compose_async_start.ansible_job_id }}\" }

register: compose_job_result

until: compose_job_result.finished \# 循环直到异步任务完成

retries: \"{{ async_poll_retries }}\"

delay: \"{{ async_poll_delay_seconds }}\"

ignore_errors: true \# 即使轮询超时或异步任务失败,也不在此处中止
Playbook

\- name: \"\[Cleanup\] 2.5 检查 Docker Compose 最终部署结果\"

block:

\- name: \"获取 Docker Compose 异步任务的最终执行状态\"

ansible.builtin.async_status: { jid: \"{{
compose_async_start.ansible_job_id }}\", mode: status }

register: final_job_result_check

\- name: \"设定 Docker Compose 部署成功标志\"

ansible.builtin.set_fact:

deployment_succeeded: \"{{ final_job_result_check.finished \|
default(false) and final_job_result_check.rc \| default(-1) == 0 }}\"

\- name: \"报告 Docker Compose 部署成功信息\"

ansible.builtin.debug: { msg: \"Docker Compose 部署成功!日志文件位于:
{{ compose_log_file_on_target }}\" }

when: deployment_succeeded

\- name: \"报告 Docker Compose 部署失败并显示错误详情\"

block:

\- name: \"获取 Docker Compose 部署失败时的日志内容\"

ansible.builtin.slurp: { src: \"{{ compose_log_file_on_target }}\" }

register: failed_log_content

ignore_errors: true \# 即使日志读取失败也继续

\- ansible.builtin.fail: \# 强制 Playbook 在此主机失败

msg: \|

Docker Compose 部署失败!

任务是否完成: {{ final_job_result_check.finished \| default(\'N/A\') }}

返回码: {{ final_job_result_check.rc \| default(\'N/A\') }}

标准输出(STDOUT): {{ final_job_result_check.stdout \| default(\'N/A\')
}}

标准错误(STDERR): {{ final_job_result_check.stderr \| default(\'N/A\')
}}

日志文件内容 ({{ compose_log_file_on_target }}):

{{ failed_log_content.content \| b64decode \|
default(\'日志内容不可用或为空。\') }}

when: not deployment_succeeded

always: \# 无论部署成功与否,都尝试执行

\- name: \"清理 Docker Compose 部署日志 (若部署成功)\"

ansible.builtin.file: { path: \"{{ compose_log_file_on_target }}\",
state: absent }

when: deployment_succeeded \| default(false) \# 仅当成功时删除

tags:

\- project_deploy

\# \-\-- 阶段三:安装 GPUSstack \-\--

\- name: GPUSstack \| 安装 GPUSstack 服务

block:

\- name: \"3.1 \[Async\] 执行 GPUSstack 安装脚本
(使用基础包和指定PyPI镜像)\"

ansible.builtin.shell:

cmd: \"curl -sfL https://get.gpustack.ai \| sh -s - \--port {{
gpustack_install_port \| default(8080) }}\"

\# 使用 default(8080) 提供一个默认端口,允许 Inventory 中覆盖
gpustack_install_port

creates: \"{{ gpustack_binary_path }}\" \# 如果此文件已存在,则跳过安装

environment: \# 为安装脚本设置环境变量

INSTALL_PACKAGE_SPEC: \"gpustack\" \# 告知脚本安装基础 gpustack 包

INSTALL_INDEX_URL: \"{{ temp_pip_index_url }}\" \# 告知脚本使用指定的
PyPI 镜像

PIP_TRUSTED_HOST: \"{{ temp_pip_trusted_host }}\" \# 配合国内 PyPI
镜像,处理 https 信任问题

args:

executable: /bin/bash \# 确保使用 bash 执行命令

async: \"{{ gpustack_async_max_runtime_seconds }}\"

poll: 0

register: gpustack_async_install

\- name: \"\[Monitor\] 3.1.1 监控 GPUSstack 安装进度\"

ansible.builtin.async_status:

jid: \"{{ gpustack_async_install.ansible_job_id }}\"

register: gpustack_job_result

until: gpustack_job_result.finished

retries: \"{{ gpustack_async_poll_retries }}\"

delay: \"{{ gpustack_async_poll_delay_seconds }}\"

ignore_errors: true

\- name: \"\[Cleanup\] 3.1.2 检查 GPUSstack 安装最终结果\"

block:

\- name: \"获取 GPUSstack 安装异步任务的最终执行状态\"

ansible.builtin.async_status:

jid: \"{{ gpustack_async_install.ansible_job_id }}\"

mode: status

register: gpustack_final_result_check

\- name: \"设定 GPUSstack 安装成功标志\"

ansible.builtin.set_fact:

gpustack_installation_succeeded: \"{{
gpustack_final_result_check.finished \| default(false) and
gpustack_final_result_check.rc \| default(-1) == 0 }}\"

\- name: \"报告 GPUSstack 安装成功信息\"

ansible.builtin.debug:

msg: \"GPUSstack 安装成功!\"

when: gpustack_installation_succeeded

\- name: \"报告 GPUSstack 安装失败并显示错误详情\"

ansible.builtin.fail:

msg: \|

GPUSstack 安装失败!

任务是否完成: {{ gpustack_final_result_check.finished \|
default(\'N/A\') }}

返回码: {{ gpustack_final_result_check.rc \| default(\'N/A\') }}

标准输出(STDOUT): {{ gpustack_final_result_check.stdout \|
default(\'N/A\') \| trim }}

标准错误(STDERR): {{ gpustack_final_result_check.stderr \|
default(\'N/A\') \| trim }}

pipx 日志文件可能位于 /root/.local/state/pipx/log/
(请登录服务器查看具体文件名)

when: not gpustack_installation_succeeded

tags:

\- gpustack_install

\# \-\-- 无需 post_tasks 来恢复 DNS,因为 resolvectl 的设置是临时的
\-\--

给文件加上可执行的权限

执行剧本

ansible-playbook deploy_playbook2.yml -i inventory -K

发现在拉取容器时失败了 十有八九是镜像源连接失败

查看日志 果然是镜像源请求超时

后来更换了镜像源还是失败了(daocloud、USTC镜像源),按道理不可能失败的,后来查看网络 估计很有可能是DNS无法解析域名,后来更换了腾讯的公用DNS服务器

在更换后确定是DNS和镜像源的问题
拉取成功

查看

后面在安装gpustack的时候出了问题,主要问题是默认安装的情况下,脚本安装的是gpustack[audio]版本 ,依赖关系很强,考虑到每个批量部署的环境不一样,根据GPUSTACK的官方文档

相关问题的github issues:[https://github.com/gpustack/gpustack/issues/1437](https://github.com/gpustack/gpustack/issues/1437%5C)

Run server with the built-in worker.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s -

# Run server with non-default port.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --port 8080

# Run server with a custom data path.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --data-dir
/data/gpustack-data

# Run server without the built-in worker.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --disable-worker

# Run server with TLS.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --ssl-keyfile
/path/to/keyfile --ssl-certfile /path/to/certfile

# Run server with external postgresql database.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --database-url
"postgresql://username:password@host:port/database_name"

# Run worker with specified IP.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh -s - --server-url [http://myserver](http://myserver) --token mytoken --worker-ip 192.168.1.100

# Install with a custom PyPI mirror.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) |
INSTALL_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple sh -s -

# Install a custom wheel package other than releases form pypi.org.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) |
INSTALL_PACKAGE_SPEC=https://repo.mycompany.com/my-gpustack.whl sh -s -

# Install a specific version with extra audio dependencies.

curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) |
INSTALL_PACKAGE_SPEC=gpustack[audio]==0.6.0 sh -s -

选择使用 INSTALL_PACKAGE_SPEC=\"gpustack\",将安装目标简化为基础的 gpustack 包,其依赖更少,兼容性更好。

gpustack登陆密码: cat /var/lib/gpustack/initial_admin_password

至此Ansible批量安装FastGPT、GPUStack完成

附上inventory文件

\# inventory.ini

\[my_servers\]

server1.example.com ansible_user=sztu \# 使用 DNS 名称或 IP 地址

server2.example.com ansible_user=sztu

\# 如果需要为特定主机指定变量,可以这样做:

\# server1.example.com ansible_user=sztu network_interface_for_dns=ens3
gpustack_install_port=8080

\# server2.example.com ansible_user=sztu network_interface_for_dns=eth0
gpustack_install_port=9090

\# 也可以在主机行后面不写变量,而是使用 host_vars 文件

\[all:vars\]

\# ansible_ssh_private_key_file: /path/to/your/ssh_private_key \#
如果使用密钥认证

\# ansible_become_pass: your_sudo_password_here \#
不推荐明文密码,建议运行时使用 -K 或 vault 加密