RHCE(劇本和變數)

q_7發表於2024-04-01

一:ansible劇本

1:簡介

一系列ansible命令的集合,使用yaml語言進行編寫的,從上往下的執行,支援很多的特性,比如,將某個命令的狀態作為變數給其他的任務執行,變數,迴圈,判斷,錯誤糾正,可以是一個playbook或者是多個playbook執行

2:yaml基本語法

1、yaml約束

  1. 編寫yaml的時候,不能使用tab,只能使用空格(vim版本有關係,vim會自動將tab轉化為4個空格鍵)
  2. 大小寫嚴格區別,大寫是大寫,小寫是小寫;大寫的變數和小寫的變數是不一樣的
  3. 使用縮排來表示一個層級關係
  4. 使用空格來表示層級關係,空格的數量沒有限制,使用#表示註釋

2、yaml資料型別

  1. 純量:類似於變數的值,最小的單位。無法進行切割
  2. 陣列(序列、列表):一組有次序的值,每一個 -’ 就是一個列表
  3. 物件(鍵值對)字典、雜湊、對映:key=value

3:playbook

1、簡單的案例

[root@server mnt]# cat file1.yaml 
- name: touch file1
  hosts: all
  tasks:
    - name:
      file:
         path: /opt/file1
         state: touch  

2、輸出的資訊解讀

[root@server mnt]# ansible-playbook file1.yaml 

##這個是playbook的名字
PLAY [touch file1] *************************************************************
#這個是playbook第一個任務,預設的任務。用於收集遠端主機的各種資訊,Ip等
TASK [Gathering Facts] *********************************************************
ok: [client]
#工作任務
TASK [file] ********************************************************************
changed: [client]
#這個就是執行命令後的總結
PLAY RECAP *********************************************************************
client                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


#rc為0的話代表這個執行成功,不為0的話,代表這執行失敗了 

可以使用-v檢視詳細的資訊,但是最多是4個v

3、playbook執行之前的檢查

在執行之前使用命令檢查一下

#檢查語法的問題(正常的情況下)
[root@server mnt]# ansible-playbook  file1.yaml  --syntax-check

playbook: file1.yaml

#錯誤的情況
[root@server mnt]# ansible-playbook  file1.yaml  --syntax-check
ERROR! A malformed block was encountered while loading tasks: {'-name': {'file': {'path': '/opt/file1', 'state': 'touch'}}} should be a list or None but is <class 'ansible.parsing.yaml.objects.AnsibleMapping'>

The error appears to be in '/mnt/file1.yaml': line 1, column 3, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:


- name: touch file1
  ^ here

#還有一個就是語法正確但是輸出有問題的情況下
使用-C就是模擬執行的,但不是真正的執行
[root@server mnt]# ansible-playbook file1.yaml -C

PLAY [touch file1] ************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [client]

TASK [file] *******************************************************************************
ok: [client]

PLAY RECAP ********************************************************************************
client                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

  

4、多個劇本

很多個劇本寫在了一起,有很多的任務

[root@server mnt]# cat file1.yaml 
- name: touch file1
  hosts: client
  tasks:
    - name:
      file:
         path: /opt/file1
         state: touch
- name: touch file2
  hosts: server
  tasks:
    - name:
      file:
        path: /opt/file2
        state: touch

  

5、playbook結構上

1)主機或者主機組

定義play的名字,遠端操作的主機,使用者,提權等相關的配置

2)變數

定義變數,然後輸出

[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  vars:
    RHCE: rhel9
  tasks:
    - name:
      debug:
        var: RHCE
3)任務列表

就是tasks裡面的

4)handler(特殊的任務)

透過監聽某個task或者幾個task,然後這個tasks執行成功後,並且狀態是chaged,所有的任務執行成功後,最後再來執行

就是要先觸發再來執行

[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  tasks:
    - name: file1
      file:
        path: /mnt/file1
        state: touch
      notify: get_status
  handlers:
    - name: get_status
      file:
        path: /mnt/file2
        state: touch

#要使用notify這個引數,來進行監聽,類似與鍵值對的  

必須是chaged才執行,並且其他的任務也沒有出現錯誤,才行,

6、劇本出現了錯誤

現象:
[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  tasks:
    - name: file1
      shell: ls /opt/qwqwqwq
    - name: file2
      file:
        path: /mnt/file2
        state: touch

#後面的命令不會執行  
總結:

1、就是執行劇本到一半的時候,出現了錯誤,會立刻的停止(對於當前任務的主機二樣),不會影響到其他任務的主機(正常的執行),下次執行的話,就是從錯誤的地方開始執行

2、按照主機劃分,執行任務

3、具有冪等性,就是一次和多次執行的結果都一樣

  如果期望值和執行的結果一致的話,則任務就是執行成功

  如果任務執行的前後,內容上的修改(檔案的時間戳也算),那麼再次執行的任務的話,就會發生覆蓋

解決的方法

1)命令模組報錯

使用||,配置/usr/bin/true,讓rc的返回值為0

[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  tasks:
    - name: file1
      shell: ls /opt/qwqwqwq || /usr/bin/true
    - name: file4
      file:
        path: /mnt/file4
        state: touch

  

2)其他模組報錯

使用ignore_errors: true跳過錯誤的任務,繼續執行後面的任務

[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  tasks:
    - name: file1
      shell: ls /opt/qwqwqwq
      ignore_errors: true
    - name: file3
      file:
        path: /mnt/file3
        state: touch

執行後,會報錯,但是執行了後面的建立file3的操作

3)handler方法

使用handler來規避錯誤,如果有任務錯誤的話,handler也不會執行,所有的任務執行完成後,才會執行handler的task任務,強制去執行handler任務,加上一個欄位force_handlers:yes即可

#中間有錯誤,依然執行
[root@server mnt]# cat var.yaml 
- name: var
  hosts: client
  force_handlers: yes
  tasks:
    - name: touch file10
      file:
        path: /opt/file10
        state: touch
      notify: get_status
    - name: chakan
      shell: ls /opt/121212
  handlers: 
    - name: get_status
      file:
        path: /opt/file11
        state: touch 

#如果監聽的是錯誤了
那就無法執行了

二:ansibel變數的定義和引用

1:為什麼需要變數

ansible管理很多個主機的時候,就是他們的主機名或者順序不一樣什麼的,就需要使用變數來進行一個統一的管理,或者埠什麼的都可以使用變數來進行定義

1、變數有數字,字母,下劃線,組成,

2、變數名可以是字母,不能以數字開頭,嚴格區分大小寫

3、在自定義變數的時候,不要以ansible開頭,因為系統中有ansible開頭的變數

案例:

[root@server mnt]# cat v1.yml 
- name: use vars
  hosts: client
  vars:
    name_rhel: rhel9
  tasks:
    - shell: touch "/opt/{{name_rhel}}"

#使用vars來定義變數,然後使用{{}}來引用變數 

4、除錯變數的方式

需要使用debug模組來進行除錯,有2個引數來進行除錯的,一個是var,另外一個是msg,2個不能一起使用

可以顯示出變數的內容,但是其他的模組在執行劇本後,看不到返回的訊息

msg:使用{{變數名}},來進行輸出內容

var:name_rhel,age_rhel可以輸出多個變數名

#專門列印變數的內容,無法列印資訊
[root@server mnt]# cat v1.yml 
- name: use vars
  hosts: client
  vars:
    name_rhel: rhel9
  tasks:
    - name: debug
      debug:
        var: name_rhel

#mes可以列印內容,也可以列印資訊
[root@server mnt]# cat v1.yml 
- name: use vars
  hosts: client
  vars:
    name_rhel: rhel9
  tasks:
    - name: debug
      debug:
        msg: "this is a {{name_rhel}}"  

2:主機清單中定義變數

內建變數:就是這些變數ansible都自定義好了的

ansible_become類似的,這種主機清單的優先順序比配置檔案的優先順序高

1、定義主機變數

[root@server ansible]# cat hosts
client webserver=nginx
#使用ad-hoc呼叫debug模組,列印變數
client | SUCCESS => {
    "webserver": "nginx"
}  

2、定義主機組變數

#使用[主機組:vars]這樣的方式來進行定義
[root@server ansible]# cat hosts
client webserver=nginx
[servers]
client
[servers:vars]
rhelname=rhce

當然,如果主機組的變數和主機發生了衝突的話,以主機的優先順序高為主
[root@server ansible]# cat hosts
client rhelname=nginx
[servers]
client
[servers:vars]
rhelname=rhce
client | SUCCESS => {
    "rhelname": "nginx"
}

3、透過主機和主機組的目錄檔案定義變數

裡面定義的都是鍵值對的方式來定義的,注意格式的規範

與hosts檔案在同一個路徑下建立2個目錄,然後主機名為命名的檔案即可

主機的目錄hosts_vars,以主機名為命名的檔案,只有該主機能夠引用變數

mkdir /etc/ansible/host_vars
[root@server host_vars]# cat client 
name: rhel9

引用變數:
[root@server host_vars]# ansible client -m shell -a 'echo "{{name}}"'
client | CHANGED | rc=0 >>
rhel9

主機組的目錄為group_vars,只有該主機組能引用變數

mkdir /etc/ansible/group_vars
[root@server group_vars]# cat servers 
age: 100

#引用變數
[root@server group_vars]# ansible servers -m shell -a 'echo "{{age}}"'

client | CHANGED | rc=0 >>
100

2、劇本中定義變數

1:vars來定義

就是在task任務之前定義即可,可以定義多次變數

就是定義了這個變數之後,然後可以不用重複的寫這個內容,直接呼叫這個變數即可,程式碼就省略了很多,但是呢,這個變數是已經寫死了的

#Vars定義,輸出出來
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  vars:
    rname: rhel9
    rage: 80
  tasks:
    - name: use shell
      shell: echo "{{rname}} {{rage}}" > /opt/file111

#使用debug模組來進行引用變數
使用var來引用變數
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  vars:
    rname: rhel9
    rage: rrr
  tasks:
    - name: use debug
      debug:
        var: rname,rage

#使用msg來引用變數
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  vars:
    rname: rhel9
    rage: rrr
  tasks:
    - name: use debug
      debug:
        msg: this is "{{rname}} {{rage}}\n"

2:vars_files來定義

引入外部的變數檔案,外部的變數檔案的內容是字典的形式,不能使用列表的形式(-)

使用vars_files這個引數

引用的話使用 鍵.鍵的方式來引用變數即可

檔案的寫法
第一種寫法
[root@server mnt]# cat file1.yaml 
user:
  name: zhangshan
  age: 18
  sex: boy

引用變數
#使用{{user.name}}這中鍵的方式來獲取值
[root@server mnt]# cat file1.yaml 
user:
  name: zhangshan
  age: 18
  sex: boy
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  vars_files:
    - /mnt/file1.yaml
  tasks:
    - name: shell
      shell: echo "{{user.name}}" >> /opt/file111

#第二種寫法,就是一個大的字典
[root@server mnt]# cat file2.yaml 
users:
  job:
    name: aaa
    age: 90
  joe:
    name: bbb
    age: 80


#輸出
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  vars_files:
    - /mnt/file2.yaml
  tasks:
    - name: shell
      shell: echo "{{users.joe.name}}" >> /opt/file111

3:註冊變數

就是將一個任務的執行結果註冊為一個變數

使用關鍵字register去得到任務的執行結果

#就是如果使用劇本來執行任務的話,就是不顯示詳細的資訊,可以使用註冊變數來讓其顯示詳細的資訊,並且也可以按照指定的變數來進行輸出
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  tasks:
    - name: shell
      shell: ls /etc/passwd
      register: get_status
    - debug:
        var: get_status

#執行這個劇本
ok: [client] => {
    "get_status": {
        "changed": true,
        "cmd": "ls /etc/passwd",
        "delta": "0:00:00.002357",
        "end": "2024-03-26 16:43:34.086058",
        "failed": false,
        "rc": 0,
        "start": "2024-03-26 16:43:34.083701",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "/etc/passwd",
        "stdout_lines": [
            "/etc/passwd"
        ]
    }
}



可以指定要的變數
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  tasks:
    - name: shell
      shell: ls /etc/passwd
      register: get_status
    - debug:
        var: get_status.rc

#一般應用的場景就是需要收集被控節點的資訊,並且將其儲存到主控節點上面
#使用這個變數來對其進行操作
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  tasks:
    - name: shell
      shell: ls /etc/passwd
      register: get_status
    - copy:
       content: "{{get_status.rc}}"
       dest: /opt/file222

案例:就是收集被控節點的資訊,然後複製到主控節點上面去

先使用copy模組,將資訊存放到被控節點上面,在使用fetch模組,將資訊存放到主控節點上面

  

4:命令模式來定義變數

使用臨時定義的變數,非常的有用

[root@server mnt]# ansible --help|grep EXTRA_VARS
               [-e EXTRA_VARS] [--vault-id VAULT_IDS]
  -e EXTRA_VARS, --extra-vars EXTRA_VARS

#使用劇本的方式,臨時定義變數
[root@server mnt]# cat v1.yaml 
- name: use vars
  hosts: client
  tasks:
    - name: file
      file:
        path: "{{path}}"
        state: touch

#如果有多個變數的話,就使用雙引號加上逗號
[root@server mnt]# ansible-playbook  v1.yaml -e "path=/opt/eeee"


#使用ad-hoc來定義變數
[root@server mnt]# ansible client -m debug -a 'var=rhel_name' -e rhel_name=90
client | SUCCESS => {
    "rhel_name": "90"
}  

5:fact變數

事實變數,就是收集被控節點的主機的資訊,然後定義一個變數,透過setup模組可以收集被控節點的主機資訊,然後透過setup模組中中的facts引數,專門用來進行收集主機資訊,透過這種方式收集到的資訊稱為facts變數(事實變數)

ansible_facts變數中有很多的資訊,主機名、網路卡裝置、ip地址、磁碟和磁碟空間、檔案系統bios版本,架構等

[root@controller ansible]# ansible node1 -m setup| head -n 10
node1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.25.250.20"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fea3:688c"
        ],
        "ansible_apparmor": {
            "status": "disabled"
很多的資訊會被輸出來  

執行playbook的時候,預設會收集被控節點的主機資訊(gather facts)

1、可以使用filter進行過濾

注意的就是隻能過濾出ansible_facts的下一層級的變數,如果有多個層級的話,不能進行收集

1)透過指定的方式進行收集

使用的變數名就是精確的

[root@controller ansible]# ansible node1 -m setup -a "filter=ansible_all_ipv4_addresses"
node1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.25.250.20"
        ],
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}  

2)透過萬用字元來收集資訊

[root@controller ansible]# ansible node1 -m setup -a "filter=ansible_*addresses"
node1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.25.250.20"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fea3:688c"
        ],
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}
2、匯出facts變數到檔案,引用facts變數

就是將變數檔案匯出來,然後存放到被控節點上面去

直接的引用fact變數,因為的話,就是在執行劇本的時候,會預設的收集這些資訊,直接輸出想要的資訊即可

#匯出facts變數
2鐘的方式進行匯出
//儲存到主控節點上面鵝,但是都是一行,不容易的看
[root@controller nod1-setup.txt]# ansible node1 -m setup --tree /opt/nod1-setup.txt

第二種方式直接使用重定向的,直接重定向到一個檔案裡面去即可
[root@controller opt]# ansible node1 -m setup > /opt/node1setup.txt
內容不在同一行上,可以使用搜尋


#引用變數
[root@controller opt]# cat facts.yml 
- name: facts
  hosts: node1
  tasks:
    - debug:
        var: ansible_all_ipv4_addresses

直接使用debug模組來進行引用
3、禁用facts變數的收集

預設是開啟的收集,禁用收集

[root@controller opt]# cat facts.yml 
- name: facts
  hosts: node1
  gather_facts: no
  tasks:
    - debug:
        var: ansible_all_ipv4_addresses
#再次執行這個劇本的時候,就會報錯,說沒有定義這個變數
ok: [node1] => {
    "ansible_all_ipv4_addresses": "VARIABLE IS NOT DEFINED!"
}

使用模組,還是能夠收集資訊,但是這種情況不常見  

[root@controller opt]# cat facts.yml 
- name: facts
  hosts: node1
  gather_facts: no
  tasks:
    - setup:
    - debug:
        var: ansible_all_ipv4_addresses

#執行劇本
ok: [node1] => {
    "ansible_all_ipv4_addresses": [
        "172.25.250.20"
    ]
}  
4、自定義的facts事實變數

讓每一個主機都有自己的facts變數,每一個facts變數都是存放到/etc/ansible/facts.d目錄下

定義的格式有要求的:ini或者yaml,檔案的字尾必須是.fact結尾(ini格式的話就是網路卡的配置檔案的格式)

自定義facts事實變數

#建立一個自定義變數的檔案,字尾為fact結尾的
[root@controller mnt]# cat userinfo.fact 
[user]
name = zhangsan
age = 18
sex = boy


#建立yaml檔案
#並將變數檔案檔案複製過去
[root@controller mnt]# cat userinfo.yaml 
- name: user fact
  hosts: node1
  tasks:
    - name: create dir
      file:
        path: /etc/ansible/facts.d
        state: directory
    - name: copy
      copy:
        src: /mnt/userinfo.fact
        dest: /etc/ansible/facts.d

#引用變數自定義變數檔案
#自定義變數檔案使用ansible_local
[root@controller mnt]# ansible node1 -m setup -a "filter=ansible_local"
node1 | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "userinfo": {
                "user": {
                    "age": "18",
                    "name": "zhangsan",
                    "sex": "boy"
                }
            }
        },
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}

6:set_fact模組

可以生成一個變數,獲取不同主機的版本,然後可以使用shell模組,來將這個變數的值儲存到本地的主機上面去

將rhel和版本拼接在一起,然後賦值給另外的一個變數,直接引用這個fact事實變數,然後進行拼接

使用setup模組,來進行拼接,賦值給一個變數,使用debug模組來進行輸出
[root@controller mnt]# cat set.yaml 
- name: setfact
  hosts: node1
  tasks:
    - set_fact:
        get_version: "{{ ansible_distribution }}--{{ansible_distribution_version}}"
    - debug:
        var: get_version
ok: [node1] => {
    "get_version": "RedHat--9.0"
}  

7:lookup變數

都還是就需要set_fact這個模組,來生成一個變數,來使用lookup這個引數,為這個變數賦值

在某些的情況下,需要引用外部的內容來作為內容,可以將主控節點的公鑰作為變數,然後傳輸到被控節上面去

lookup變數能夠從檔案,命令,變數中作為變數的值

1、從檔案中賦值變數

格式: get_passwd:"{{loopup('file','/etc/passwd')}}" 就是將主控節點這個值賦給了get_passwd這個變數

案例:

#將一個/etc/passwd這個檔案複製到被控節點上面
#這個就是將這個檔案裡面的內容都複製到這個被控節點的檔案上面去了
[root@controller mnt]# cat look.yml 
- name: look
  hosts: node1
  tasks:
    - set_fact:
       fff: "{{lookup('file','/etc/passwd')}}"
    - name: copy
      copy:
        content: "{{fff}}"
        dest: /opt/passwd

2、從命令中賦值給變數

格式:get_passwd:"{{lookup('pipe','date +%T')}}"

就是將命令的結果作為變數的值

#天劍一個使用者,並且密碼是redhat
#使用這個來進行一個密碼的加密,然後賦值給pd這個變數名,最後使用user模組,來引用這個變數
[root@controller mnt]# cat user.yml 
- name: create user
  hosts: node1
  tasks:
    - name: passwd
      set_fact:
        pd: "{{lookup('pipe','openssl passwd -6 redhat')}}"
    - name: created user
      user:
        name: q1000
        password: "{{pd}}"

3、從變數中賦值

就是從環境變數中進行賦值

#直接引用這個環境變數
#最後使用debug模組來進行輸出
[root@controller mnt]# cat env.yml 
- name: env
  hosts: node1
  tasks:
    - name: env
      set_fact:
        get_env: "{{lookup('env','HOME')}}"
    - name: debug
      debug:
       msg: "{{get_env}}"


ok: [node1] => {
    "msg": "/root"
}

8:魔法變數

就是內建的變數, 內建變數有特殊含義的就被稱為魔法變數

fact變數只有執行的主機才能夠呼叫,就是隻想要所有的被控節點都呼叫node1的主機的主機名

1、hostvars

獲取指定主機的變數資訊,可以使用主機名或者主機ip地址(主機清單和- hosts中指定都是ip地址才可以使用ip地址),讓所有的主機都獲取主機node1的主機名,說到底還是獲取的是facts變數的內容,setup模組中的

案例:

#在很多的主機名中指定輸出一個主機名即可
[root@controller mnt]# cat magic.yaml 
- name: magic
  hosts: node1
  tasks:
    - debug:
       msg: "{{hostvars['node1'].ansible_default_ipv4.address}}"

2、inventory_hostname

列出當前執行的任務的主機(常常和when判斷使用)

when是一個判斷語句,判斷是不是當前的主機名,如果不是則不執行任務,是的話,就執行任務

#當前的主機名是node1的話就執行
- name: magic
  hosts: node1
  tasks:
    - debug:
       var: ansible_hostname
      when: inventory_hostname == 'node1'

ok: [node1] => {
    "ansible_hostname": "node1"
}

3、groups

groups列出當前主機清單中的所有的主機組,groups.all列出所有的主機(常用於迴圈)

group_names:顯示當前執行的任務的主機屬於哪個主機組(可以執行,就是這個任務只在這個主機上面執行,) when test in group_names 當執行的主機是test的時候,就執行任務,否則不執行任務,跳過

groups.web列出web主機組的主機

group.all就是收集當前主機清單中的所有主機,所以的話就是如果沒有收集到的話,就會報錯

案例:

#列出主機清單中的所有的主機組和主機
#groups就能列出所有的
- name: magic
  hosts: node1
  tasks:
    - debug:
       var: groups

#列出所有的主機
- name: magic
  hosts: node1
  tasks:
    - debug:
       var: groups.all


#列出web中的主機
- name: magic
  hosts: node1
  tasks:
    - debug:
       var: groups.web

  

  

總結:

1、錯誤的補救的方法

  ignore_errors,忽略這個錯誤,然後繼續執行下一個任務

  handlers這個強制的使用,透過監聽的方式

2、變數

vars和vars_files,命令模式來定義變數這些都是隻能定義一些普通的變數

facts就是收集被控節點的主機的資訊

register註冊變數就是收集命令的執行結果返回的資料,可以指定結果的輸出

set_fact變數:就是可以生成一個變數,

  可以根據fact變數直接呼叫,然後賦值

  lookup直接賦值,就是直接使用一個值

  自定義賦值

魔法變數的話:也是內建的變數,只不過有特殊含義的變數,可以輸出指定的主機名,指定的主機組,以及和when常常使用的能輸出當前的是哪一個主機組

  

相關文章