首先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 加密