ansible 自动化运维工具(三)playbook脚本

打印 上一主题 下一主题

主题 890|帖子 890|积分 2670

目次
Playbook的定义
Playbook组成
Playbook命令
Playbook脚本编写格式
基本组件
Handlers处理惩罚器
tags标签
Facts组件
Register:注册变量
Debug模块
Playbook循环
With_items循环
With_dict循环(字典循环)
With_nested循环(嵌套循环)
Loop循环
playbook条件
when判定
Black:定义使命组
Ansible变量
通过vars定义变量
通过vars_files定义变量
通过变量目次定义变量
通过命令行定义变量


Playbook的定义

Playbook着实是Ansible服务的一个设置文件,Ansible使用Playbook的YAML语言设置编写成操作需求,实现对远端主机的计谋部署,以及对远端主机的控制与管理。如果说单个模块实行类似于Linux系统中的命令,那么Playbook就类似于shell脚本,将多个模块组合起来实现一组的操作。
Playbook组成

脚本(Playbook):
Playbook是一个或多个"play"的聚集。
一个playbook文件可以包含多个play,每个play定义了一组要实行的使命。

剧目(Play):
一个play定义了一组要实行的使命,以及这些使命实用的主机或主机组。
Play包含一个或多个使命(task),这些使命按顺序实行。
每个play开始时,可以指定要应用使命的主机或主机组,以及一些可选的设置,如变量、脚色等。
(这里可以如许来理解,play就相当于是shell中的一个脚本,shell≈playbook,而shell中可以有许多脚本,以是playbook中也可以有许多play,而task就相当于是shell中封装的函数,一个脚本中可以有多个函数,以是play中也可以有多个task,每个task就是为了实现一个功能)

其核心元素

使命(Task):
使命是playbook的基本实行单元。
每个使命都是一个模块的调用,用于在目标主机上实行特定的操作。
使命可以著名称(name),用于形貌使命的目的。
使命可以使用变量来参数化模块的参数。

变量(Variable):
变量用于存储和传递playbook中的动态值。
变量可以在playbook的差别位置定义,如inventory文件、play中的vars部分、使命中的参数等。
变量可以在使命中使用Jinja2模板语法举行引用和操作。
条件和循环:
Ansible支持在使命中使用条件语句(when)来控制使命的实行。
可以根据变量的值、主机的状态等条件来决定是否实行某个使命。
Ansible还支持使用循环(loop)来重复实使用命,可以遍历列表或字典等数据布局。
处理惩罚程序(Handler):
处理惩罚程序是一种特殊的使命,在满意特定条件时触发实行。
处理惩罚程序通常用于重启服务、重新加载设置等操作。
使命可以使用notify关键字来通知处理惩罚程序,当使命实行成功时,处理惩罚程序将被触发。
模板(Template):
模板是一种使用Jinja2模板语言编写的文件。
模板允许你在文件中使用变量、条件语句、循环等功能,动态生成设置文件。
Ansible的template模块用于将模板文件渲染并复制到目标主机上。

Playbook命令

实行脚本的格式:
  1. ansible-playbook [参数] playbook.yml
复制代码
常用参数:
参数
 剖析
-T
建立SSH连接的超时时间
-i
指定Inventory文件
-f
并发实行的进程数,默以为5
- -list-hosts
匹配的服务器列表
- -list-tasks
列出使命列表
- -step
每实行一个使命后制止,等候用户确认
- -syntax-check
语法检测
- -list-tags
列出此yml文件中的全部tag标签
- -skip-tags
实行–skip-tags之外的标签使命
-C
查抄当前这个Playbook是否会修改受控端,模拟实行
Playbook脚本编写格式

从上文定义可知,playbook脚本的设置文件为YAML格式的文件
YAML的格式如下:
   

  • .文件的第一行应该以“---”(三个连字符)开始,表明YAML文件的开始。
  • 常见的缩进为4格方式,也可以使用 2 个空格或制表符,但要保持同等。
  • 在同一行中,#之后的内容表现注释,类似于shell,python和ruby。
  • YAML中的列表元素以“-”开头而且跟着一个空格。背面为元素内容。
  • 同一个列表之中的元素应该保持雷同的缩进,否则会被当做错误处理惩罚。
  • play中hosts、variables、roles、tasks等对象的表现方法都是以键值中心以“:”分隔表现,而且“:”之后要加一个空格。
  基本组件

这里通过一个http.yml文件来对组件举行剖析
  1. ---
  2. - name: apache
  3.   hosts: web01
  4.   remote_user: root
  5.   become: yes
  6.   tasks:
  7.     - name: Install httpd Server
  8.       yum:
  9.         name: httpd
  10.         state: present
复制代码
name定义一个Playbook的名称,用于标识Playbook的用途;
hosts指定要在哪个主机上实行,也是写主机或主机组名,必要提前在/etc/ansible/hosts中设置好;
remote_user: 指定playbook运行时的用户身份,可以写在hosts下,也可以每个tasks做定义;
become:yes表现使用特权用户;一样平常和remote_user一起使用
tasks属于是一个使命列表,重要写具体实行什么的(可以有多个);
 name每个使命的名称,用于形貌干什么的;上述yml中则是安装httpd服务;
  yum表现使用哪个模块来举行操作;模块的参数可以看ad-hoc中的,用的都是一样的,写法不一样就是;
    name要安装的服务名称,我们这里是httpd:
    state要举行的操作,可以是安装、卸载、更新;

生产情况中为了可读性与可维护性通常一个playbook中只编写一个play,如果某些主机必要实行多个play,那么可以使用include关键字在一个playbook中导入其他的playbook。
Handlers处理惩罚器

Handlers 不会自动实行,只有当被notify语句调用时才会实行。
handlers和notify指定的名称必须雷同,否则无法触发。
handlers 中必要- name指定名称 ,handlers只会在全部的tasks实行完后实行,而且,即便一个handlers被触发多次,也只会实行一次。 handlers是一种特殊的tasks。

Handlers多用于重启服务等操作
下面举一个redis示例:
  1.   ---
  2.   - name: redis
  3.     hosts: web01
  4.     tasks:
  5.     - name: install redis
  6.       yum:
  7.         name: redis
  8.         state: installed
  9.     - name: copy_file
  10.       copy:
  11.         src: /etc/redis.conf
  12.         dest: /etc/redis.conf.bak
  13.       #给任务打标签
  14.       tags: copy_file
  15.       #触发开关:触发的名称
  16.       notify: restart
  17.     - name: start
  18.       service:
  19.         name: redis
  20.         state: started
  21.         enabled: yes
  22.     #触发后的一个动作
  23.     handlers:
  24.     - name: restart
  25.       service:
  26.         name: redis
  27.         state: restarted
复制代码

测试是否能够启动


tags标签

这里举例到了tags组件,就把tags来讲一下吧
默认情况下,Ansible在实行一个playbook时,会实行playbook中定义的全部使命;Ansible playbook中的tags标签是一种用于选择性运行特定使命或使命集的机制。通过为每个使命指定标签,您可以在运行playbook时选择只运行带有特定标签的使命,而不运行其他使命。这对于控制和管理Ansible playbook的实行非常有用,特殊是当playbook中包含许多使命时。

如果只必要运行单独的使命就可以通过标签指定

通过使命列表可以看到它只实行了copy模块
Facts组件

Ansible facts是在被管理主机上通过Ansible自动收罗发现的变量。facts包含每台特定的主机信息。比如:被控端的主机名、IP地点、系统版本、CPU数量、内存状态、磁盘状态等等。

前面setup模块也说过可以指定facts组件的内置变量去获取主机信息,原理是一样的
使用shell模块引用facts组件变量,存储输出的信息,使用debug把存储的信息输出到终端
  1. [root@localhost ansible]# cat facts.yml
  2. ---
  3. - hosts: web01
  4.   tasks:
  5.     - shell: echo {{ ansible_memory_mb }}
  6.       register: my_memory
  7.     - debug:
  8.         var: my_memory.stdout_lines
复制代码
setup模块输出和playbook使用facts输出

上述编写的脚本中用到register和debug,下面对此展开解说
Register:注册变量

它用于将一个使命的实行结果存储到一个变量中,以便在后续的使命中可以引用这个变量,举行条件判定、输出表现或者其他操作。这个变量可以包含使命实行的返回码、标准输出、标准错误输出等信息。常和debug结合使用,register用于存储,debug用于输出。
  1. [root@web01 ansible]# cat register.yml
  2. ---
  3. - hosts: web01
  4.   tasks:
  5.     - shell: echo "这是一个register"
  6.       register: shell_print
  7.     - debug:
  8.         var: shell_print.stdout_lines
复制代码
测试

Debug模块

debug模块在 Ansible Playbook 中重要用于输出调试信息,资助你相识变量的值、使命实行情况等内容,以便排查问题、验证设置是否符合预期。它可以将指定的变量值或者自定义的消息打印出来,使得整个 Playbook 的实行过程更加的清晰
常用参数:
   msg调试输出的消息
  var: 将某个使命实行的输出作为变量传递给debug模块,debug会直接将其打印输出,一样平常会和register的变量共同输出
  verbosity: debug的级别(默认是0级)
  这里举例msg参数,自定义信息
  1. [root@web01 ansible]# cat debug.yml
  2. ---
  3. - hosts: web01
  4.   tasks:
  5.     - yum: name=httpd state=installed
  6.     - debug:
  7.         msg: "httpd安装完成"
复制代码


Playbook循环

With_items循环

with_items是 Ansible 中最常用的循环机制之一。它允许你在一个使命中对一个列表中的每个元素实行雷同的操作。这个列表可以包含各种数据类型,如字符串、数字、字典等。
这里直接举实例
批量创建用户:
  1. [root@web01 ansible]# cat user.yml
  2. ---
  3.    - name: creat
  4.      hosts: web01
  5.      tasks:
  6.        - name: create user
  7.          user:
  8.            name: "{{ item }}"
  9.            state: present
  10.          with_items:
  11.            - ha1
  12.            - ha2
  13.            - ha3
复制代码
验证:

With_dict循环(字典循环)

由于ansible是由python开发的,以是ansible字典完全服从python字典的定义
字典即由键值对组成,键值对的键和键值对的值讲究 一 一对应,且在同一个字典中键必须唯一,值恣意。
键值对格式:item.key:item.value


  • 概念:当你有一个字典数据布局,而且想对字典中的每个键值对举行操作时,可以使用with_dict循环。在循环中,item.key表现字典的键,item.value表现字典的值。

这里有新添的内容,还没讲到,着实前面主机清单提到过,大致用法雷同不多做赘述,大致先来讲一下
Vars定义了一个变量services,这个变量里包含两个字典service1,service2,他们下面分别有两个键值对
  1. [root@web01 ansible]# touch /etc/services.conf
  2. [root@web01 ansible]# cat dict.yml
  3. ---
  4.    - name: using a dictionary
  5.      hosts: web01
  6.      vars:
  7.        services:
  8.          service1:
  9.            port: 8080
  10.            protocol: tcp
  11.          service2:
  12.            port: 8888
  13.            protocol: udp
  14.      tasks:
  15.        - name: Configure Service Ports
  16.          lineinfile:
  17.            path: /etc/services.conf
  18.            line: "{{ item.key }} {{ item.value.port }}/{{ item.value.protocol }}"
  19.          with_dict: "{{ services }}"
复制代码

With_nested循环(嵌套循环)



  • with_nested是 Ansible 中用于实现嵌套循环的一种方式。它允许你在一个使命中对多个嵌套的列表举行迭代,生成全部大概的组合来实使用命。这在处理惩罚多层数据布局,必要对组合后的元素举行操作时非常有用。

例如:有两个列表,一个是服务器类型列表(server_types),另一个是数据中心列表(data_centers),你想要为每个服务器类型在每个数据中心创建一个设置文件。
  1. [root@web01 playbook_file]# mkdir /root/configs
  2. [root@web01 playbook_file]# cat nested.yml
  3. ---
  4.      - name: with_nested 循环
  5.        hosts: web01
  6.        vars:
  7.          server_types: ["web", "db"]
  8.          data_centers: ["dc1", "dc2"]
  9.        tasks:
  10.          - name: Create config files
  11.            command: touch /root/configs/{{ item.0 }}_{{ item.1 }}.conf
  12.            with_nested:
  13.              - "{{ server_types }}"
  14.              - "{{ data_centers }}"
复制代码


通过这个结果可以得到item.0在这里代表着server_types列表里的全部元素,item.1代表着data_centers列表的全部元素,以是末了才得到了这4个设置文件,且是举行的有序创建,那么可以得到item可以同时包含多个值,且为有序序列,以是item是一个元组。
Loop循环



  • 概念:是 Ansible 2.5 版本引入的一种更通用的循环布局。它可以用于遍历多种数据类型,包括列表(list)、字典(dict)等,对嵌套也能举行操作,提供了一种同一的循环语法,相当于是with_*的升级版。
保举使用loop循环,它能够更好地与 Ansible 的其他新特性(如条件判定、标签等)相结合,而with_items在一些复杂场景大概会受限制。

也可以参考官方文档:循环 — Ansible 社区文档 - Ansible 文档

   常用过滤器:
  

  • select / reject:这两个过滤器允许您根据条件选择或排除列表中的元素。
  • unique:去除重复项元素
  • flatten:列表包含其他列表,您可以使用 flatten 来将全部子列表中的元素提取出来归并成一个新的列表,具有一个可选参数levels,用于指定必要归并几层嵌套的列表
  • dict2items loop本身默认的数据格式通常是一个列表,以是必要将字典转换为包含键 - 值对的列表形式
  • product在我理解来看,这个过滤器重要用于盘算两个或多个列表时的的组合问题,在数学中称它为笛卡尔积,例如,若 A = {1,2},B = {3,4},则 A×B = {(1,3),(1,4),(2,3),(2,4)}这4个组合。
  
  1. 基本格式:loop :{{变量名 | 过滤器(可选)}}
复制代码

使用loop循环,遍历字典时,必要使用dict2items过滤器,将其转换成可被loop遍历的列表形式,其中每个元素都是一个包含key和value属性的对象
  1. [root@web01 ansible]# cat dict.yml
  2. ---
  3.    - name: Configure services using a dictionary
  4.      hosts: web01
  5.      vars:
  6.        services:
  7.          service1:
  8.            port: 8080
  9.            protocol: tcp
  10.          service2:
  11.            port: 8888
  12.            protocol: udp
  13.      tasks:
  14.        - name: Configure Service Ports
  15.          lineinfile:
  16.            path: /etc/services.conf
  17.            line: "{{ item.key }} {{ item.value.port }}/{{ item.value.protocol }}"
  18.          loop: "{{ services | dict2items }}"
复制代码

使用loop做一个嵌套循环
这里使用了product过滤器,末了通过list将结果组合转化为列表
  1. [root@web01 playbook_file]# cat loop.yml
  2. ---
  3.        - name: Loop with custom variable name
  4.          hosts: localhost
  5.          vars:
  6.            my_list: ["apple", "banana", "cherry"]
  7.            your_list: ["orange", "peach", "pear"]
  8.          tasks:
  9.            - name: Print elements with custom loop variable
  10.              debug:
  11.                msg: "This is what you and I like to eat {{ item.0 }}_{{ item.1 }}"
  12.              loop: "{{ my_list|product(your_list)|list }}"
复制代码

playbook条件

when判定

在 Ansible - Playbook 中,when语句用于条件判定,它决定了使命是否实行。只有当when条件为真时,对应的使命才会被实行。

通过判定内置变量是否正确,从而下载服务
  1. [root@web01 playbook_file]# cat when.yml
  2. ---
  3.        - name: System judgment
  4.          hosts: web01
  5.          tasks:
  6.            - shell: echo {{ ansible_os_family }}
  7.              register: my_family          #存储shell输出的变量
  8.            - debug:
  9.                var: my_family.stdout       #打印内置变量
  10.            - name: Install server
  11.              yum:
  12.                name: redis
  13.                state: installed
  14.              when: ansible_os_family == "RedHat"
复制代码

通过定义变量判定下载服务
  1. [root@web01 playbook_file]# cat when2.yml
  2. ---
  3.   - name: Install a package
  4.     hosts: web01
  5.     vars:
  6.       install_package: true
  7.     tasks:
  8.     - name: Install
  9.       yum:
  10.         name: httpd
  11.         state: installed
  12.       when: install_package
复制代码


Black:定义使命组



  • 在 Ansible 2.4 及以上版本中,block通常用于组织一些做重复使命的布局。它允许将多个相关的使命组合在一起,形成一个逻辑单元。这个逻辑单元可以看作是一个使命块,这些使命要么全部成功实行,要么在出现错误时按照特定的错误处理惩罚机制(如rescue和always)举行处理惩罚。

通过black定义使命块
  1. [root@web01 playbook_file]# cat black.yml
  2. ---
  3.      - name:  block
  4.        hosts: web01
  5.        vars:
  6.          install_package: true
  7.          my_info: "文件已创建"
  8.        tasks:
  9.          - name: block
  10.            block:
  11.              - name: Task 1 in block
  12.                file:
  13.                  path: /root/httpd.conf
  14.                  state: touch
  15.              - name: Task 2 in block
  16.                debug:
  17.                  msg: "{{ my_info }}"
  18.            when: install_package
复制代码

通过rescue举行错误处理惩罚
如果block中的使命堕落,rescue中的使命会实行。
  1. [root@web01 playbook_file]# cat black.yml
  2. ---
  3.      - name:  block
  4.        hosts: web01
  5.        vars:
  6.          install_package: true
  7.          my_info: "文件已创建"
  8.        tasks:
  9.          - name: block
  10.            block:
  11.              - name: Task 1 in block
  12.                file:
  13.                  path: /root/httpd.conf
  14.                  state: touch
  15.              - name: Task 2 in block
  16.                debug:
  17.                  msg: "{{ 错误变量 }}"  #通过自定义一个不存在的变量,让task2,出错
  18.            when: install_package
  19.            rescue:
  20.              - name: other task
  21.                debug:
  22.                  msg: "前面任务执行失败了,现在通过rescue处理"
复制代码

通过always处理惩罚举行追加使命
always中的使命无论block中的使命成功与否都会实行。
  1. [root@web01 playbook_file]# cat black.yml
  2. ---
  3.      - name:  block
  4.        hosts: web01
  5.        vars:
  6.          install_package: true
  7.          my_info: "文件已创建"
  8.        tasks:
  9.          - name: block
  10.            block:
  11.              - name: Task 1 in block
  12.                file:
  13.                  path: /root/httpd.conf
  14.                  state: touch
  15.              - name: Task 2 in block
  16.                debug:
  17.                  msg: "{{ 错误变量 }}"
  18.            when: install_package
  19.            rescue:
  20.              - name: other task
  21.                debug:
  22.                  msg: "前面任务执行失败了,现在通过rescue处理"
  23.            always:
  24.              - name: add task
  25.                debug:
  26.                  msg: "{{ my_info }}"
复制代码

Ansible变量

着实前面已经对变量有许多的应用了,不论是在主机清单中设置变量,照旧在play文件中设置变量
这里重要对playbook中的变量举行解说
变量的定义方式

  • 通过命令行举行变量定义
  • 在play文件中举行变量定义
  • 通过Inventory主机信息文件中举行变量定义
变量读取的优先级为: 命令行 > playbook文件 > Inventory文件
通过vars定义变量

通过vars定义变量packages_name,下载mariadb-server服务
  1. ---
  2.  
  3.   - name: mysql
  4.     hosts: web01
  5.     vars:
  6.       packages_name:
  7.       - mariadb-server
  8.     tasks:
  9.       - name: install mysql
  10.         yum:
  11.           name: "{{packages_name}}"
  12.           state: present
复制代码


通过vars_files定义变量

当变量较少时,使用vars定义没有问题,当变量较多时,可以将变量保存到一个独立的文件中;
创建一个专门保存变量的文件
  1. [root@web01 playbook_file]# cat my_vars.yml
  2. ---
  3.   httpd_package: httpd
复制代码
通过主文件调用变量文件
 
  1. [root@web01 playbook_file]# cat vars.yml
  2. ---
  3.  
  4.   - name: mysql
  5.     hosts: web01
  6.     vars:
  7.       packages_name:
  8.       - mariadb-server
  9.     vars_files:
  10.       - my_vars.yml      #调用文件操作
  11.     tasks:
  12.       - name: install mysql
  13.         yum:
  14.           name: "{{packages_name}}"
  15.           state: present
  16.       - name: install httpd
  17.         yum:
  18.           name: "{{ httpd_package }}"
  19.           state: present
复制代码


通过变量目次定义变量

这里必要留意这是官方制订的定义方法以是目次名字不能做修改
主机目次:host_vars
在host_vars目次中定义的变量,只能给对应的主机使用,没有定义变量的主机不能使用此处的变量
host_vars目次,然后在创建一个文件夹,文件的文件名称要与inventory清单中的主机名称要保持完全同等,如果是IP地点,则创建雷同IP地点的文件即可。

在ansible目次下创建一个host_vars目次
  1. [root@web01 ansible]# tree
  2. .
  3. ├── ansible.cfg
  4. ├── hosts
  5. ├── host_vars                   #目前的一个目录结构
  6. │   └── web01   # 此为文件,非目录
  7. [root@web01 ansible]# cat host_vars/web01
  8. ---
  9.   my_info: "这是定义在host_vars中的变量"
复制代码
主机组目次:group_vars
在项目目次中创建group_vars目次,然后在创建一个文件,文件的文件名称要与清单文件中定义的组名保持完全同等。
在ansible目次下创建一个group_vars目次
  1. [root@web01 ansible]# tree
  2. .
  3. ├── ansible.cfg
  4. ├── group_vars
  5. │   └── web_group
  6. ├── hosts
  7. ├── host_vars
  8. │   └── web01
  9. [root@web01 ansible]# cat group_vars/web_group
  10. group_info: "这是在group_vars里的信息"
复制代码

主机清单:inventory.ini
ansible-playbook命令提供-i选项,用于在命令行定义主机清单(inventory.ini),命令行定义主机清单的优先级最高。
命令行主机清单(inventory.ini)> /etc/ansible/hosts
  1. [root@web01 ansible]# tree
  2. .
  3. ├── ansible.cfg
  4. ├── group_play.yml
  5. ├── group_vars
  6. │   └── web_group
  7. ├── hosts
  8. ├── host_vars
  9. │   └── web01
  10. ├── inventory.ini
复制代码
主机清单的设置
设置这个inventory.ini文件如果直接填写主机名,必要在/etc/hosts文件添加映射,且必要给填写主机设置免密登录
  1. [root@web01 ansible]# cat inventory.ini
  2. #主机
  3. web01
  4. web02
  5. #主机组
  6. [web_group]
  7. web01
  8. web02
复制代码
末了创建play.yml文件,来调用主机变量host_vars/web01
  1. ├── ansible.cfg
  2. ├── group_vars
  3. │   └── web_group
  4. ├── hosts
  5. ├── host_vars
  6. │   └── web01
  7. ├── inventory.ini
  8. ├── play.yml
  9. [root@web01 ansible]# cat play.yml
  10. ---
  11.   - name: info
  12.     hosts: web01
  13.     tasks:
  14.       - name: 记录信息
  15.         debug:
  16.          msg: "{{ my_info }}"
复制代码
 
实行play.yml文件

创建group_play.yml文件,来调用主机变量group_vars/web_group
  1. [root@web01 ansible]# tree
  2. .
  3. ├── ansible.cfg
  4. ├── group_play.yml
  5. ├── group_vars
  6. │   └── web_group
  7. ├── hosts
  8. ├── host_vars
  9. │   └── web01
  10. ├── inventory.ini├── play.yml[root@web01 ansible]# cat group_play.yml
  11. ---
  12.   - name: info
  13.     hosts: web_group
  14.     tasks:
  15.       - name: 记录信息
  16.         debug:
  17.           msg: "{{ group_info}}"
复制代码

实行group_play.yml文件

通过命令行定义变量

ansible-playbook命令提供-e选项,用于在命令行定义变量,命令行定义变量的优先级最高。
  1. [root@web01 ansible]# cat group_play.yml
  2. ---
  3.   - name: info
  4.     hosts: web_group
  5.     tasks:
  6.       - name: 记录信息
  7.         debug:
  8.           msg: "{{ group_info}}"
复制代码
命令行定义变量,临时替代group_vars/web_group中的变量
  1. [root@web01 ansible]# ansible-playbook -i inventory.ini group_play.yml -e "group_info="这是在命令行定义的变量""
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

杀鸡焉用牛刀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表