Ansible自動化管理工具


一、Ansible 基礎

1.Ansible基礎概述

1.1 什么是Ansible

Ansible是一個IT自動化的配置管理工具,自動化主要體現在Ansible集成了豐富模塊,豐富的功能組件,可以
通過一個命令行完成一系列的操作。進而能減少我們重復性的工作和維護成本,以提高工作的效率。

1.2 Ansible可以完成哪些功能呢

1)批量執行遠程命令,可以對N多台主機同時進行命令的執行
2)批量配置軟件服務,可以進行自動化的方式配置和管理服務。
3)實現軟件開發功能,jumpserver底層使用ansble來實現的自動化管理0
4)編排高級的IT任務,Ansible的playbook是一門編程語言,可以用來描繪一套IT架構。

1.3 Ansible的特點

1.容易學習,無代理模式,不像saltstack既要學服務端又要學習客戶端,還要學習服務端與客戶端之間的通訊協議
2.操作靈活,體現在Ansible有較多的模塊,提供了豐富的功能,playbook則提供了類似於編程語言的復雜功能
3.簡單易用,體現在Ansible —個命令可以完成很多事情
4.安全可靠,因為Ansible使用了SSH協議進行通汛,既穩定又安全
5.可移植性高,可以將寫好的playbook拷貝至任意機器進行執行
4.Ansible架構中的控制節點、被控制節點、inventroy, ad-hoc、playbook、連接協議是什么?

2.Ansible的安全配置

2.1.Ansible安裝

外網地址 內網地址 角色
10.0.0.61 172.16.1.61 Ansible控制端
10.0.0.7 172.16.1.7 Ansible被控端
10.0.0.8 172.16.1.8 Ansible被控端

1.先安裝epel源(提供最新的ansible)

wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

2.安裝Ansible

yum install ansible -y

查看ansible的版本
[root@m01 ~]# ansible --version
ansible 2.7.7
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]

3.Ansible的配置文件,配置文件可以隨意放,但有查找順序

$ANSIBLE_CONFIG
ansible.cfg				#當前目錄下面查找
.ansible.cfg 				#當前用戶的家目錄下查找
/etc/ansible/ansible.cfg

[root@m01 ~]# cat /etc/ansible/ansible.cfg 
#inventory      = /etc/ansible/hosts      #主機列表配置文件
#library        = /usr/share/my_modules/  #庫文件存放目錄
#remote_tmp     = ~/.ansible/tmp          #臨時py文件存放在遠程主機目錄
#local_tmp      = ~/.ansible/tmp          #本機的臨時執行目錄
#forks          = 5                       #默認並發數
#sudo_user      = root                    #默認sudo用戶
#ask_sudo_pass = True                     #每次執行是否詢問sudo的ssh密碼
#ask_pass      = True                     #每次執行是否詢問ssh密碼
#remote_port    = 22                      #遠程主機端口
host_key_checking = False                 #跳過檢查主機指紋
log_path = /var/log/ansible.log           #ansible日志

[privilege_escalation]					#如果是普通用戶則需要配置提權
#become=True
#become_method=sudo
#become_user=root
#become_ask_pass=False

二、Ineventory主機清單

1.場景一、基於密碼連接

[root@oldboy.com ~]# cat /etc/ansible/hosts
#方式一、主機+端口+密碼
[webservers]
172.16.1.7 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass='123456'
172.16.1.8 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass='123456'

#方式二、主機+端口+密碼
[webservers]
web[1:2].oldboy.com ansible_ssh_pass='123456'

#方式三、主機+端口+密碼
[webservers]
web[1:2].oldboy.com
[webservers:vars]
ansible_ssh_pass='123456'

2.場景二、基於密鑰連接,需要先創建公鑰和私鑰,並下發公鑰至被控端

[root@m01 ~]# ssh-keygen
[root@m01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.16.1.7
[root@m01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.16.1.8

-----------------------------------------------------------
[root@m01 ~]# cat hosts 
#方式一、主機+端口+密鑰
[webservers]
172.16.1.7
172.16.1.8

[root@m01 ~]# ansible webservers -m ping -i ./hosts 
172.16.1.8 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
172.16.1.7 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

-----------------------------------------------------------
[root@m01 ~]# cat hosts 
#方式二、別名+主機+端口+密鑰
[webservers]
web01 ansible_ssh_host=172.16.1.7 ansible_ssh_port=22
web02 ansible_ssh_host=172.16.1.8


[root@m01 ~]# ansible webservers -m ping -i ./hosts 
web02 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
web01 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

3.場景三、主機組使用方式

#1.定義兩個組
[lbservers]
172.16.1.5
172.16.1.6

[webservers]
172.16.1.7
172.16.1.8

#2.servers組包括兩個子組[lbservers,webserver]
[servers:children]
[lbservers]
[webserver]


#列出當前某個組有多少台主機
[root@m01 ~]# ansible lbservers -m ping -i ./hosts --list-hosts
  hosts (1):
    web01
[root@m01 ~]# ansible webservers -m ping -i ./hosts --list-hosts
  hosts (1):
    web02
[root@m01 ~]# ansible servers -m ping -i ./hosts --list-hosts
  hosts (2):
    web01
    web02
[root@m01 ~]# ansible all -m ping -i ./hosts --list-hosts
  hosts (3):
	web03
    web02
    web01

注意:
如果控制端和被控制端第一次通訊,需要先添加指紋信息,那如果機器特別多少的情況下怎么辦?
僅需開啟ansible中的 host_key_checking = False

三、Ad-Hoc

1.常用模塊

command                 執行shell命令(不支持管道等特殊字符)
shell                         執行shell命令
scripts                      執行shell腳本
yum_repository        配置yum倉庫
get_url                     聯網下載		
yum                         安裝			
copy                        配置			
service、systemd   啟動			
user、group            創建用戶與組	
file                           授權			
crond                       定時任務		
mount                      掛載			
firewalld		        firewall
selinux			selinux

2..使用過程中需要先了解ansible-doc幫助手冊

[root@m01 ~]# ansible-doc -l        # 查看所有模塊說明
[root@m01 ~]# ansible-doc copy      # 表示指定模塊方法
[root@m01 ~]# ansible-doc -s copy   # 表示指定模塊參數

3..command默認執行bash命令模塊,模塊不支持重定向或管道

[root@m01 ~]# ansible oldboy  -a "hostname"

4..shell模塊,如果需要一些管道操作,則使用shell

[root@m01 ~]# ansible oldboy -m shell -a "ifconfig|grep eth0" -f 50

5..script腳本模塊

[root@m01 ~]# cat yum.sh
#!/usr/bin/bash
yum install -y iftop

#在本地運行模塊,等同於在遠程執行,不需要將腳本文件進行推送目標主機執行
[root@m01 ~]# ansible oldboy -m script -a "/server/scripts/yum.sh"

6..yum安裝軟件模塊

[root@m01 ~]# ansible oldboy -m yum -a "name=httpd state=installed"
name        						
	httpd		#指定要安裝的軟件包名稱
	file://		#指定從本地哪個目錄安裝rpm
	http://		#指定從哪個網站安裝rpm包
state       			#指定使用yum的方法
	present   		#安裝軟件包
	absent      	#移除軟件包
	latest		#安裝最新軟件包 
list=ansible					#列出當前倉庫可用的軟件包
disablerepo="epel,ol7_latest"		#安裝軟件時,不從哪些倉庫獲取
download_only=true			#僅下載軟件包,不安裝

7.copy文件拷貝模塊

#1.拷貝文件文件至被控節點
[root@m01 ~]# ansible oldboy -m copy -a "src=/etc/hosts dest=/tmp/test.txt"
#2.對遠端已有文件進行備份,按照時間信息備份
[root@m01 ~]# ansible oldboy -m copy -a "src=/etc/hosts dest=/tmp/test.txt backup=yes"
#3.向被控端主機寫入數據,並且會覆蓋遠端文件內原有數據信息
[root@m01 ~]# ansible oldboy -m copy -a "content='bgx' dest=/tmp/oldboy"

src               #推送數據的源文件信息
dest             #推送數據的目標路徑
backup        #對推送傳輸過去的文件,進行備份
content        #直接批量在被管理端文件中添加內容
group           #將本地文件推送到遠端,指定文件屬組信息
owner          #將本地文件推送到遠端,指定文件屬主信息
mode           #將本地文件推送到遠端,指定文件權限信息

8..file文件創建模塊

1.直接修改被控端的權限
[root@m01 ~]# ansible web01 -m file -a "path=/opt mode=0400" -i ./hosts

2.在被控端創建目錄
[root@m01 ~]# ansible oldboy -m file -a "path=/tmp/oldboy state=directory"

3.在被控端創建文件
[root@m01 ~]# ansible oldboy -m file -a "path=/tmp/tt state=touch mode=555 owner=root group=root"

4.遞歸授權目錄權限
[root@m01 ~]# ansible oldboy -m file -a "path=/data owner=bgx group=bgx recurse=yes"
path              #指定遠程主機目錄或文件
recurse         #遞歸授權
state             #狀態
    directory   #在遠端創建目錄
    touch        #在遠端創建文件
    link            #創建鏈接文件
    absent      #表示刪除文件或目錄
    mode        #設置文件或目錄權限
    owner       #設置文件或目錄屬主
    group       #設置文件或目錄屬組

9..get_url文件下載模塊

1.通過get_url下載文件或者軟件
[root@m01 ~]# ansible webservers -m get_url -a "url=http,https  dest=/opt mode=0777" -i ./hosts

2.下載一個文件前先進行md5校驗,通過則下載,不通過則失敗
ansible webservers -m get_url -a "url=http,https  dest=/opt mode=0777 checksum=md5:76eb3af80ffd" -i ./hosts
url			#文件在網絡上的具體位置
dest		        #下載到被控端的哪個目錄下
checksum	#校驗(md5  sha256)

10.ansible管理服務的啟動與停止,使用service、systemd

#1.啟動crond服務,並加入開機自啟
[root@m01 ~]# ansible webservers -m service -a "name=crond state=started enabled=yes"
#2.停止crond服務,並刪除開機自啟
[root@m01 ~]# ansible webservers -m service -a "name=crond state=stopped enabled=no"
#3.重啟crond服務
[root@m01 ~]# ansible webservers -m service -a "name=crond state=restarted"
#4.重載crond服務
[root@m01 ~]# ansible webservers -m service -a "name=crond state=reloaded"

name        # 定義要啟動服務的名稱
state       # 指定服務狀態
    started     #啟動服務
    stopped     #停止服務
    restarted   #重啟服務
    reloaded    #重載服務
enabled         #開機自啟	

11.group組模塊

[root@m01 ~]# ansible webservers -m group -a "name=oldgirl gid=888"
name            #指定創建的組名
gid             #指定組的gid
state
    absent      #移除遠端主機的組
    present     #創建遠端主機的組(默認)

12.user模塊

1.創建用戶指定uid和gid,不創建家目錄也不允許登陸
[root@m01 ~]# ansible oldboy -m user -a "name=oldgirl uid=888 group=888 shell=/sbin/nologin create_home=no"

2.刪除用戶
[root@m01 ~]# ansible webservers -m user -a "name=tmd state=absent" -i ./hosts 

3.給新創建的用戶生成ssh密鑰對
[root@m01 ~]# ansible webservers -m user -a "name=oo uid=6677 group=adm generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_rsa" -i ./hosts 

4.將明文密碼進行hash加密,然后進行用戶創建
[root@m01 ~]# ansible localhost -m debug -a "msg={{ '123456' | password_hash('sha512', 'salt') }}"
localhost | SUCCESS => {
    "msg": "$6$salt$MktMKPZJ6t59GfxcJU20DwcwQzfMvOlHFVZiOVD71w.igcOo1R7vBYR65JquIQ/7siC7VRpmteKvZmfSkNc69."
}
[root@m01 ~]# ansible webservers -m user -a 'name=xlw password=$6$salt$MktMKPZJ6t59GfxcJU20DwcwQzfMvOlHFVZiOVD71w.igcOo1R7vBYR65JquIQ/7siC7VRpmteKvZmfSkNc69. create_home=yes shell=/bin/bash' -i ./hosts 

uid             #指定用戶的uid
group           #指定用戶組名稱
groups          #指定附加組名稱
password        #給用戶添加密碼(記得單引號)
shell           #指定用戶登錄shell
create_home     #是否創建家目錄

13.crond定時任務模塊

# 正常使用crond服務(默認沒寫的時間都算*表示)
[root@m01 ~]# crontab -l
* * * * *  /bin/sh /server/scripts/yum.sh

# 使用ansible添加一條定時任務
[root@m01 ~]# ansible webservers -m cron -a "minute=* hour=* day=* month=* weekday=* job='/bin/sh test.sh'"
[root@m01 ~]# ansible webservers -m cron -a "job='/bin/sh /server/scripts/test.sh'"

# 設置定時任務注釋信息,防止重復,name設定
[root@m01 ~]# ansible webservers -m cron -a "name='cron01' job='/bin/sh /server/scripts/test.sh'"

# 刪除相應定時任務
[root@m01 ~]# ansible webservers -m cron -a "name='ansible cron02' minute=0 hour=0 job='/bin/sh test.sh' state=absent"

# 注釋相應定時任務,使定時任務失效
[root@m01 scripts]# ansible oldboy -m cron -a "name='ansible cron01' minute=0 hour=0 job='/bin/sh test.sh' disabled=yes"

14..mount掛載模塊7 nfs 8客戶端掛載

[root@m01 ~]# ansible web01 -m yum -a 'name=nfs-utils state=present' -i ./hosts
[root@m01 ~]# ansible web01 -m file -a 'path=/data state=directory' -i ./hosts
[root@m01 ~]# ansible web01 -m copy -a 'content="/data 172.16.1.0/24(rw,sync,no_all_squash)" dest=/etc/exports' -i ./hosts 
[root@m01 ~]# ansible web01 -m systemd -a "name=nfs state=started enabled=yes" -i ./hosts


[root@m01 ~]# ansible web02 -m mount -a "src=172.16.1.7:/data path=/data fstype=nfs opts=defaults state=present"
[root@m01 ~]# ansible web02 -m mount -a "src=172.16.1.7:/data path=/data fstype=nfs opts=defaults state=mounted"
[root@m01 ~]# ansible web02 -m mount -a "src=172.16.1.7:/data path=/data fstype=nfs opts=defaults state=unmounted"
[root@m01 ~]# ansible web02 -m mount -a "src=172.16.1.7:/data path=/data fstype=nfs opts=defaults state=absent"

present     # 開機掛載,僅將掛載配置寫入/etc/fstab
mounted     # 掛載設備,並將配置寫入/etc/fstab
unmounted   # 卸載設備,不會清除/etc/fstab寫入的配置
absent      # 卸載設備,會清理/etc/fstab寫入的配置

15.防火牆管理模塊

Selinux模塊

[root@m01 ~]# ansible webservers -m selinux -a "state=disabled" -i ./hosts

firewalld模塊

[root@m01 ~]# ansible webservers -m systemd -a "name=firewalld state=started" -i ./hosts 
[root@m01 ~]# ansible webservers -m firewalld -a "service=http immediate=yes permanent=yes state=enabled" -i ./hosts 
[root@m01 ~]# ansible webservers -m firewalld -a "port=8080-8090/tcp immediate=yes permanent=yes state=enabled" -i ./hosts 

service				#指定開放或關閉的服務名稱
port				#指定開放或關閉的端口
masquerade			#開啟地址偽裝
immediate			#臨時生效
permanent			#是否添加永久生效
state				#開啟或是關閉

zone				#指定配置某個區域
rich_rule			#配置富規則
source				#指定來源IP

四、Playbook

1.Ansible Playbook基本概述

1.什么是playbook,playbook翻譯過來就是“劇本' 那么playbook組成如下

playbook:定義一個文本文件,以yml為后綴結尾(翻譯:我有一個劇本)
play:定義的是主機的角色(翻譯:找哪個大腕明星)
task:定義的是具體執行的任務(翻譯:大腕每一集拍什么)

總結:playbook是由一個或多個play組成,一個play可以包含多個task任務。
可以理解為:使用不同的模塊來共同完成一件事情。

2.Ansible playbook與AD-Hoc的關系

  1. playbook是對AD-Hoc的一種編排方式。
  2. playbook可以持久運行,而Ad-Hoc只能臨時運行。
  3. playbook適合復雜的任務,而Ad-Hoc適合做快速簡單的任務。
  4. playbook能控制任務執行的先后順序,以及互相依賴的關系。

3.Ansible Playbook書寫格式

playbook是由yml語法書寫,結構清晰,可讀性強,所以必須掌握yml基礎語法

語法 描述
縮進 YAML使用固定的縮進風格表示層級結構,每個縮進由兩個空格組成,不能使用Tab
冒號 以冒號結尾的除外,其他所有冒號后面所有必須有空格。
短橫線 表示列表項,使用一個短橫杠加一個空格。多個項使用同樣的縮進級別作為同一列表。

3.1.下面我們一起來編寫一個playbook文件,playbook起步

host:對哪些主機進行操作
remote_user:我要使用什么用戶執行
tasks: 具體執行什么任務

[root@m01 ~]# cat f1.yml 
#play
- hosts: webservers

  tasks:
    - name: Installed Httpd Server
      yum:
        name: httpd
        state: present

    - name: Start Httpd Server
      systemd:
        name: httpd
        state: started
        enabled: yes

2.檢查語法,只檢查是否是yaml語法格式。並不做邏輯校驗。

[root@m01 project1]# ansible-playbook --syntax-check p1.yml 

playbook: p1.yml

3.模擬執行(不是真的執行)

[root@m01 project1]# ansible-playbook -C  p1.yml 

4.真實的描述狀態(被控端的狀態必須與控制端描述的狀態一致)

[root@m01 project1]# ansible-playbook   p1.yml 

3.2.多paly語法示例

ansible安裝並配置httpd服務,根據不同的主機配置不同的網站。(多個play的使用方式,但不是生產推薦,了解項,生產推薦使用循環方式)

[root@m01 project1]# cat p1.yml 
---
#play
- hosts: webservers

  tasks:
    - name: Installed Httpd Server
      yum: name=httpd state=present

    - name: Start Httpd Server
      systemd: name=httpd state=started enabled=yes

    - name: Start Firewalld Server
      systemd: name=firewalld state=started enabled=yes

    - name: Configure Firewalld Server
      firewalld: service=http immediate=yes permanent=yes state=enabled

- hosts: web01
  tasks:
    - name: Configure web01 Website
      copy: content='This is Web01' dest=/var/www/html/index.html

- hosts: web02
  tasks:
    - name: Cofnigure webi-2 weisite
      copy: content='This is Web02' dest=/var/www/html/index.html

3.3.安裝nfs服務

1.安裝
2.配置
	用戶
	/data
3.啟動
#記得重啟你的nfs
[root@m01 project1]# cat nfs.yml 
- hosts: web01

  tasks:
    - name: Install NFS-utils Server
      yum: name=nfs-utils state=present

    - name: Configure Nfs-utils Server
      copy: src=./exports.j2 dest=/etc/exports owner=root group=root mode=0644

    - name: Create NFS Group
      group: name=www gid=666

    - name: Create NFS User
      user: name=www uid=666 group=www create_home=no shell=/sbin/nologin

    - name: Create Data Directory
      file: path=/data state=directory owner=www group=www mode=0755 recurse=yes

    - name: Start NFS Server
      systemd: name=nfs state=started enabled=yes

- hosts: web02
  tasks:
    - name: Mount NFS Server
      mount: path=/opt src=172.16.1.7:/data fstype=nfs opts=defaults state=mounted

3.4.使用AnsiblePlaybook方式構建LAMP架構,具體操作步驟如下:

1.使用yum安裝 httpd、php、php-mysql、mariadb、firewalld等
2.啟動httpd、firewalld、mariadb等服務
3.添加防火牆規則,放行http的流量,並永久生效
4.使用get_url下載 http://fj.xuliangwei.com/public/index.php 文件

[root@m01 project1]# cat lamp.yml 
#- hosts: webservers
- hosts: otherservers
  tasks:
    - name: Installed Web Packages
      yum: name=httpd,mariadb-server,php,php-mysql,php-pdo state=present

    - name: Start Web Serivce
      service: name=httpd state=started

    - name: Start Mariadb Service
      service: name=mariadb state=started

    - name: Get Wordpress
      unarchive: src=./wordpress-5.0.3-zh_CN.tar.gz dest=/var/www/html/ copy=yes mode=0755


  #  - name: Copy Index.php
  #    copy: src=./index.php.j2 dest=/var/www/html/index.php


 #   - name: Get Url index.php
 #     get_url: url="http://fj.xuliangwei.com/public/index.php" dest=/var/www/html/index.php

4.Ansible Playbook變量解析

1 .變量概述

變量提供了便捷的方式來管理ansible項目中的動態值。比女Ozabbix-3.4.15,可能后期會反復的使用到這個版本的值,那么如果將此值設置掃變量,后續使用和修改都將變得非常方便。這樣可以簡化項目的創建和維護
定義變量分為如下三種方式
1)通過命令行進行變量定義
2)在play文件中進行定義變量
3)通過inventory在主機組或單個主機中設置變量

如果定義的變量出現重復,且造成沖突,優先級如下:
1.命令行定義的變量-高於->play文件定義的變量-高於->inventory文件定義的變量。

2.變量的定義

2.1.playbook變量可以通過多種方式進行定義,最簡單的方式就是在playbook的開頭通過vars進行定義

#安裝兩個軟件包使用變量方式
[root@m01 project1]# cat  p2.yml 
- hosts: webservers
  vars:
    - web_package: httpd
    - ftp_package: vsftpd
  tasks:
    - name: Installed Packages
      yum: 
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present

2.2.也可以在playbook中使用vars_files指定文件作為變量文件,好處就是其他的playbook也可以調

[root@m01 project1]# cat vars.yml 
web_package: httpd
ftp_package: vsftpd

[root@m01 project1]# cat p2.yml 
- hosts: webservers
  vars_files: ./vars.yml
  tasks:
    - name: Installed Packages
      yum: 
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present

2.3.在inventory中定義變量,主機變量優先級高於主機組變量(不推薦,容易將環境弄的特別亂)

[root@m01 project1]# vim /etc/ansible/hosts 
[webservers]
web01 ansible_ssh_host=172.16.1.7
web02 ansible_ssh_host=172.16.1.8
[webservers:vars]
filename=group_vars

[root@m01 project1]# cat p3.yml 
- hosts: webservers
  tasks:
    - name: Create File
      file: path=/tmp/{{ filename }} state=touch

2.4.更好的方式是在ansible的項目目錄中創建額外的兩個變量目錄,分別是host_vars和group_vars

group_vars目錄下必須存放和inventory清單文件中定義的組名一致,如下
[root@m01 project1]# cat /etc/ansible/hosts 
[webservers]
web01 ansible_ssh_host=172.16.1.7
web02 ansible_ssh_host=172.16.1.8

[root@m01 project1]# cat group_vars/webservers 
web_package: httpd
ftp_package: vsftpd
注意:系統提供了特殊的組,all,也就說在group_vars目錄下創建一個all文件,定義變量對所有的主機都生效


[root@m01 project1]# cat host_vars/web01 
web_package: zlib-static
ftp_package: zmap

[root@m01 project1]# cat group_vars/webservers 
web_package: httpd
ftp_package: vsftpd


[root@m01 project1]#  cat p4.yml 
- hosts: webservers
#- hosts: otherservers
  tasks:
    - name: Installed Packages
      yum: 
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present


[root@m01 project1]# ansible-playbook p4.yml 

PLAY [webservers] ********************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************
ok: [web02]
ok: [web01]

TASK [Installed Packages] ************************************************************************************************
ok: [web02]
changed: [web01]

PLAY RECAP ***************************************************************************************************************
web01                      : ok=2    changed=1    unreachable=0    failed=0   
web02                      : ok=2    changed=0    unreachable=0    failed=0   

2.5.通過命令行覆蓋變量,inventory的變量會被playbook文件中覆蓋,這兩種方式的變量都會被命

令行直接指定變量所覆蓋。使用--extra-vars或-e設定變量。

[root@m01 project1]# ansible-playbook p4.yml -e "web_package=zarafa-devel" -e "ftp_package=zarafa-utils"

2.6.變量優先級測試

命令行變量--->play中的vars_files--->play中的vars變量-->host_vars中定義的變量--->group_vars/組--->group_vars/all

[root@m01 project1]# cat p5.yml 
- hosts: webservers
#  vars:
#    filename: play_vars
#  vars_files:
#    - ./vars.yml
  tasks:

    - name: Create 
      shell: mkdir -pv /tmp/{{ filename }}
      register: mk_test

    - name: debug
      debug: msg={{ mk_test }}

2.7.變量注冊register

- hosts: webservers
  tasks:
    - name: Get Network Port Status
      shell: netstat -lntp
      register: net_port

    - name: OutPut Network Port Status
      debug:
        msg: "{{ net_port.stdout_lines }}"

2.8.變量也支持層級定義,使用"."可能會有問題,建議使用"[]"代替。

[root@m01 project1]# cat vars1.yml 
rainbow:
  web:
    web_package: httpd
    db_package: mariadb

code:
  web:
    filename: code_web_filename

	
[root@m01 project1]# cat p8.yml 
- hosts: webservers
  vars_files: ./vars1.yml
  tasks:
    - name: Install Package
      yum: name= "{{ rainbow['web']['web_package'] }}"

    - name: create filename
      file: 
        path: /tmp/{{ code.web.filename }}
        state: touch

2.9.facts變量 (setup模塊)

Ansible facts是在被管理主機上通過ansible自動采集發現的變量。facts包含每台特定的主機信息。比如:被控端主機的主機名、IP地址、系統版本、CPU數量、內存狀態、磁盤狀態等等。
facts使用場景
1.通過facts檢查CPU,來生成對應的Nginx配置文件
2.通過facts檢查主機名信息,來生成不同的Zabbix配置文件
3.通過fact檢索的內存情況來自定義mysql的配置文件

1.facts基本用法,比如獲取被控端的主機名與IP地址

[root@m01 〜]# cat facts.yml
-hosts: web
  tasks:
    -name: Output variables ansible facts
      debug:
        msg: >
           this default IPv4 address "{{ ansible_fqdn}}" is "{{ ansible_default_ipv4.address}}"

2.使用facts模擬批量修改zabbix配置文件,template和copy用法差不多,但template支持解析變量

[root@m01 project1]# cat p10.yml 
- hosts: webservers
  #gather_facts: no   關閉facts采集
  vars: 
    - zabbix_server: 172.16.1.71
  tasks:
    - name: Copy Zabbix Agent Configure
      template: src=./zabbix_agentd.conf dest=/tmp/zabbix_agent.conf
  1. facts開啟后會影響Ansible主機的性能,如果沒有采集被控端主機需求可選擇關閉
[root@m01 project1]# cat p10.yml 
- hosts: webservers
  #gather_facts: no   關閉facts采集
  vars: 
    - zabbix_server: 172.16.1.71
  tasks:
    - name: Copy Zabbix Agent Configure
      template: src=./zabbix_agentd.conf dest=/tmp/zabbix_agent.conf

4.使用setup模塊獲取采集的值

ansible web01 -m setup -i ./hosts

5.playbook安裝一個memcached,利用了facts變量

[root@m01 project1]# cat memcached.j2 
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="{{ ansible_memtotal_mb //2 }}"
OPTIONS=""

[root@m01 project1]# cat p11.yml 
- hosts: webservers
  tasks:
    - name: Installed Memcached
      yum: name=memcached state=present

    - name: Configure Memcached
      template: src=./memcached.j2 dest=/etc/sysconfig/memcached

    - name: Start Memcached
      service: name=memcached state=started enabled=yes

2.10.批量修改主機名

解法一、web_隨機數的解法

[root@m01 ~]# cat te.yaml
- hosts: all
  tasks:
  - name: 打印facts變量的內容
    debug: msg={{ ansible_default_ipv4.address }}
  - name: 使用hostname模塊將主機名修改為web_ip
    hostname: name=web_{{ ansible_default_ipv4.address }}

解法二、web_隨機數的解法

[root@m01 ~]# cat te_2.yaml
- hosts: all
  tasks:
    - name: 定義一個隨機數,設定為變量,然后后續調用
      shell: echo $((RANDOM%200))
      register: System_SJ

    - name: 使用debug輸出變量結果,這樣好知道需要提取的關鍵值
      debug: msg={{ System_SJ }}

    - name: 使用hostname模塊將主機名修改為web_隨機數
      hostname: name=web_{{ System_SJ.stdout }}

解法三、隨機數+時間戳的解法

[root@m01 project1]# cat vars_14.yml 
- hosts: oldboy
  tasks:

    - name: SHell
      shell: echo $RANDOM|md5sum |cut -c 5-10
      register: get_random

    - name: Get Facts
      debug:
        msg: "{{ ansible_date_time.epoch }}"

    - name: Hostname
      hostname: name={{ get_random.stdout }}_{{ ansible_date_time.epoch }}

5.Playbook條件語句

判斷在Ansible任務中_吏用頻率非常高。比如yum模塊可以檢測軟件包是否已被安裝,而在這個過程中我們不用做太多的人工干預。

但是也有部分任務需要進行判斷,比如:web服務器角色都需要安裝nginx倉庫,但其他的服務器角色並不需要,此時就會用到when判斷。

比如:Centos與Ubuntu系統都需要安裝httpd服務,那么就需要使用when判斷主機系統,然后調用不同的模塊執行。

實踐案例一、根據不同操作系統,安裝相同的軟件包
Centos:httpd
Ubuntu:httpd2

[root@m01 project2]# cat when.yml 
- hosts: webservers
  tasks:

    - name: Install httpd Server
      yum: name=httpd state=present
      when: ansible_distribution == "CentOS"

    - name: Install httpd Server
      apt: name=httpd2 state=present
      when: ansible_distribution == "Ubuntu"

實踐案例二、所有為web主機名的添加nginx倉庫,其余的都跳過添加
1.如何添加yum倉庫
2.如何判斷,判斷什么內容

---
- hosts: all
  tasks:
    - name: Add Nginx Repos
      yum_repository:
        name: nginx_tet
        description: Nginx YUM repo
        baseurl: http://nginx.org/packages/centos/7/$basearch/
        gpgcheck: no
      when: (ansible_hostname is match ("web*")) or (ansible_hostname is match ("lb*"))

1.通過register將命令執行結果保存至變量,然后通過when語句進行判斷

- hosts: webservers
  tasks:
    - name: Check Httpd Server
      command: systemctl is-active httpd
      ignore_errors: yes
      register: check_httpd

    #- name: debug outprint			#僅僅只是輸出結果
    #  debug: var=check_httpd

    - name: Httpd Restart
      service: name=httpd state=restarted
      when: check_httpd.rc == 0

6.Playbook循環語句

有時候我們寫playbook的時候發現了很多task都要重復引用某個模塊,比如一次啟動10個服務,或者一次拷貝10個文件,如果按照傳統的寫法最少要寫10次,這樣會顯得playbook很臃腫。如果使用循環的方式來編寫playbook,這樣可以減少重復使用某個模塊。

實踐案例一、使用循環啟動多個服務

[root@m01 project2]# cat with.yml 
- hosts: webservers
  tasks:
    - name: Start httpd mariadb
      systemd: name={{ item }} state=started
      with_items:
        - httpd
        - mariadb

案例二、使用定義變量方式循環安裝軟件包。

- hosts: webservers
  tasks:
    - name: ensure a list of packages installed
      yum: name= "{{ packages }}" state=present
      vars:
        packages:
         - httpd
         - httpd-tools

#棄用的方式
- hosts: webservers
  tasks:
    - name: ensure a list of packages installed
      yum: name= "{{ item }}" state=present
      with_items:
        - httpd
        - httpd-tools

實踐案例三、使用字典循環方式創建用戶和批量拷貝文件

1.批量創建用戶,使用字典的key value的方式
[root@manager ~]# cat loop-user.yml
- hosts: webservers
  tasks:
    - name: Add Users
      user: name={{ item.name }} groups={{ item.groups }} state=present
      with_items:
        - { name: 'testuser1', groups: 'bin' }
        - { name: 'testuser2', groups: 'root' }
		

2.使用字典循環批量拷貝文件
[root@m01 project2]# cat with4.yml 
- hosts: webservers
  tasks:
    - name: Copy Rsync configure and Rsync passwd
      copy: src={{ item.src }} dest={{ item.dest }} mode={{ item.mode }}
      with_items:
        - { src: "./rsyncd.conf", dest: "/etc/rsyncd.conf", mode: "0644" }
        - { src: "./rsync.passwd", dest: "/tmp/rsync.passwd", mode: "0600" }

7.handlers觸發器

1.當配置發生改變,重啟服務

[root@m01 project2]# cat han.yml 
- hosts: webservers
  vars:
    - http_port: 8083
  tasks:

    - name: Install Http Server
      yum: name=httpd state=present

    - name: configure httpd server
      template: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
      notify: 
        - Restart Httpd Server
        - Restart PHP Server

    - name: start httpd server
      service: name=httpd state=started enabled=yes

  handlers:
    - name: Restart Httpd Server
      systemd: name=httpd state=restarted 

    - name: Restart PHP Server
      systemd: name=php-fpm state=restarted

2.handlers注意事項

  • 1.無論多少個task通知了相同的handlers,handlers僅會在所有tasks結束后運行一次。
  • 2.只有task發生改變了才會通知handlers,沒有改變則不會觸發handlers
  • 3.不能使用handlers替代tasks

8.Playbook tag標記(用於調試的場景下)

默認情況下,Ansible在執行一個playbook時,會執行playbook中定義的所有任務。Ansible的標簽(Tags)功能可以給單獨任務甚至整個playbook打上標簽,然后利用這些標簽來指定要運行playbook中的個別任務,或不執行指定的任務。
1.打標簽的方式有幾種,比如:
對一個task打一個標簽、對一個task打多個標簽、對多個task打一個標簽

2、對task打完標簽應該如何使用

-t:執行指定的tag標簽任務
-skip-tags:執行-skip-tags之外的標簽任務

[root@m01 project2]# cat tag.yml 
- hosts: webservers
  vars:
    - http_port: 8083
  tasks:

    - name: Install Http Server
      yum: name=httpd state=present
      tags: 
        - install_httpd
        - httpd_server

    - name: configure httpd server
      template: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
      notify: Restart Httpd Server
      tags: 
        - confiure_httpd
        - httpd_server

    - name: start httpd server
      service: name=httpd state=started enabled=yes
      tags: service_httpd

  handlers:
    - name: Restart Httpd Server
      systemd: name=httpd state=restarted 

[root@m01 project2]# ansible-playbook tag.yml --list-tags                                    #查看所有標簽
[root@m01 project2]# ansible-playbook tag.yml -t httpd_server                            #運行指定的標簽
[root@m01 project2]# ansible-playbook tag.yml -t install_httpd,confiure_httpd    #運行指定的多個標簽
[root@m01 project2]# ansible-playbook tag.yml --skip-tags httpd_server            #跳過指定的標簽

9.Playbook 文件復用(include)

include用來動態的包含tasks任務列表include_tasks新版/include老版

include包含
include(import_playbook)此為包含完整的文件
include_tasks 此為包含任務

#主入口文件
[root@m01 project2]# cat task.yml 
- hosts: webservers
  vars:
    - http_port: 801

  tasks:
    - include_tasks: task_install.yml
    - include_tasks: task_configure.yml
    - include_tasks: task_start.yml

  handlers:
    - name: Restart Httpd Server
      systemd: name=httpd state=restarted

#任務文件
[root@m01 project2]# cat task_install.yml 
- name: Install Http Server
  yum: name=httpd state=present

[root@m01 project2]# cat task_configure.yml 
- name: configure httpd server
  template: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  notify: Restart Httpd Server

[root@m01 project2]# cat task_start.yml 
- name: start httpd server
  service: name=httpd state=started enabled=yes

10.Playbook忽略錯誤(ignore_errors)

默認Playbook會偷asks執行的返囪狀態,如遇到鍺誤則會立即終止playbook的后續的tasks執行。然而有些時候palybook即使執行鍺誤了也要讓其繼續執行

加入參數:ignore_errors: yes忽略錯誤

編寫playbook,當有task執行失敗則會立即終止后續task運行

[root@manager ~]# cat f9.yml
---
- hosts: webservers
  tasks:
    - name: Ignore False
      command: /bin/false
      ignore_errors: yes
	  
    - name: touch new file
      file: path=/tmp/bgx_ignore state=touch

11.Playbook異常處理

通常情況下,當task失敗后,play將會終止,任何在前面已經被tasks notify的handlers都不會被執行。如果你在play中設置了force_handlers: yes參數,被通知的handlers就會被強制執行。(有些特殊場景可能會使用到)

force_handlers: yes 強制調用handlers (寫到play里)
changed_when: false 被管理主機沒有發生變化,可以使用參數將change狀態改為ok
changed_when: httpd_check.stdout.find('OK') #查看變量中的某個字符串

[root@m01 project2]# cat changed_when.yml 
- hosts: webservers
  vars:
    - http_port: 8083
  tasks:

    - name: configure httpd server
      template: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
      notify: Restart Httpd Server

    - name: Check HTTPD
      shell: /usr/sbin/httpd -t
      register: httpd_check
      changed_when: 
	    - httpd_check.stdout.find('OK')
		- false

    - name: start httpd server
      service: name=httpd state=started enabled=yes

  handlers:
    - name: Restart Httpd Server
      systemd: name=httpd state=restarted 

failed_when
命令不依賴返回狀態碼來判定是否執行失敗,而是要查看命令返回內容來決定,比如返回內容中包括 failed 字符串,則判定為失敗。示例如下:

- name: this command prints FAILED when it fails 
  command: /usr/bin/example-command -x -y -z 
  register: command_result 
  failed_when: "'FAILED' in command_result.stderr"

五、Ansible加密模塊

1.Ansible Vault 概述

Ansible Vault作為ansible的一項新功能可將例如passwords,keys等感數據文件逬行加密,而非存放在明文的playbooks或roles中。

2.Ansible Vault實踐

ansible加密使用的是ansible-vault命令進行加密,語法示例

[root@m01 project2]# ansible-vault --help
Usage: ansible-vault [create|decrypt(解密)|edit|encrypt(加密)|encrypt_string|rekey(修改密碼)|view(查看)] [options] [vaultfile.yml]

加密一個文件

ansible-vault encrypt include.yml

查看一個文件

[root@m01 project2]# ansible-vault view include.yml 
Vault password: 
- import_playbook: han.yml
- import_playbook: when2.yml 

修改加密的文件內容

[root@m01 project2]# ansible-vault edit include.yml

rekey 修改密碼

[root@m01 project2]# ansible-vault rekey include.yml 
Vault password: 
New Vault password: 
Confirm New Vault password: 
Rekey successful

執行加密的playbook

echo "1" >pass
chmod 600 pass 
ansible-playbook include.yml  --vault-password-file=pass

六、jinja模板

1.Ansible Jinja2模板概述

1.1.什么是Jinja2

Jinja2是Python的全功能模板引擎

1.2.Jinja2橫板與Anslble有什么關系

Ansible通常會使用Jinja2模板來修改被管理主機的配置文件。例如給10台遠程主機都裝上httpd服務,但是要求每個服務器的端口不一樣,如何解決?

1.3.Anslble如何使用Jinja2模板

使用ansible的jinja2模板,也就是template模板。該模塊和copy模塊一樣,都是將文件復制到遠端主機上去,但是區別在於template模板可以獲取要復制的文件中變量的值,而copy則是原封不動的把文件內容復制過去,比如:針對不同的主機定義不同的變量, template會在將配置文件分發出去前讀取變量到iinja2模板.然后分發到不同的被管理主機上。

Ansible 使用Jinja2模板注意事項:
Ansible允許Jinja2模板中使用條件判斷和循環.但是不允許在playbook中使用。
注悤:不是每個管理員都需要這個特性,但屋有些時候jinja2模板能大大提高效率。

2.Ansible Jinja2的基本使用

2.1.jinja模板基本語法

{{EXPR}}輸出變量的值(會輸出自定義變量的值或fact}
1)playbook文件使用template 參數
2)模板文件里面變量使用{{名稱}},比如{{PORT}}或使用facts.

2.2.Jinja模板邏輯關系

{% for i in EXPR %}...{% endfor%}作為循環表達式
{% if EXPR %}...{% elif EXPR %}...{% endif%}作為條件判斷
{# COMMENT #}表示注釋

2.3.jinja模板使用示例,使用fact變量的示例

1)使用Playbook推送文件

[root@m01 playbook ]# cat jinja2.yml
- hosts: web
  tasks:
    - name: Copy Template File /etc/motd
      template: src=./motd.j2 dest=/etc.motd

2)准備motd.j2文件

[root@m01 playbook]# cat motd.j2
Welcome to {{ ansible_hostname }}
This system total Memory is: {{ ansible_memtotal_mb }} MB
This system free Memory is: {{ ansible_memfree_mb }} MB

3)執行Playbook

[root@m01 playbook]# ansible-playbook -i hosts jinja2.yml

3.Ansible Jinja2管理Nginx

ansible 使用jinja2的for循環表達式渲染出nginx負載均衡的配置文件

1)使用Playbook推送nginx配置文件
[root@m01 project2]# cat jinja_nginx.yml 
- hosts: webservers
  vars:
    - http_port: 80
    - server_name: www.oldboyedu.com
  tasks:
    - name: Copy Nginx COnfigure
      template: src=./oldboyedu.conf.j2 dest=/etc/nginx/conf.d/oldboyedu_proxy.conf

2)准備proxy.conf.j2配置文件
[root@m01 project2]# cat proxy.conf.j2 
upstream {{ server_name }} {
{% for i in range(1,20) %}
  server 172.16.1.{{i}}:{{http_port}};
{%endfor%}
}
server {
	listen {{ http_port }};
	server_name {{ server_name }};
	location / {
		proxy_pass http://{{ server_name }};
		proxy_set_header Host $http_host;
	}
}

循環的高級用法:
讀取主機清單中的組(inventory)

upstream {{ server_name }} {
{% for i in groups['webserver'] %}
    server {{i}}:{{http_port}} weight=2;
{% endfor %}

4.Ansible Jinja2管理Keepalived

ansible 使用jinja2的if判斷表達式渲染出Keepalived高可用配置文件,並推送到lb組

1)使用Playbook推送Keepalived配置文件
[root@m01 project2]# cat jinja_keepalived.yml 
- hosts: webservers
  tasks:
    - name: Copy Keepalived Configure
      template: src=./kee.conf.j2 dest=/tmp/keepalived.conf

2)准備kee.conf.j2 配置文件
[root@m01 project2]# cat kee.conf.j2 
global_defs {     
    router_id {{ ansible_hostname }}
}

vrrp_instance VI_1 {
{%if ansible_hostname =="web01" %}
    state MASTER
    priority 150
{%elif ansible_hostname == "web02" %}
    state BACKUP
    priority 100
{%endif%}

    interface eth0
    virtual_router_id 50
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
}
    virtual_ipaddress {
        10.0.0.3
    }
}

5.Ansible Jinja2 IF 管理mysql

1)使用Playbook推送mysql配置文件
[root@m01 project2]# cat jinja_mysql.yml 
- hosts: webservers
  gather_facts: no
  vars:
    PORT: 13306
   # PORT: false  #相當於開關
  tasks:
    - name: Copy MySQL Configure
      template: src=./my.cnf.j2 dest=/tmp/my.cnf

2)准備my.cnf.j2 配置文件
[root@m01 project2]# cat my.cnf.j2 
{% if PORT %}
bind-address=0.0.0.0:{{ PORT }}
{% else %}
bind-address=0.0.0.0:3306
{%endif%}

七、Ansible Rose角色

1.Ansible Rose基本概述

前面已經學過tasks和handles,那怎樣組織playbook才是最好的方式呢?簡單的回答就是:使用Roles! Roles基於一個已知的文件結構,去自動的加載某些vars_files, tasks以及handlers。

以便pkaybook更好的調用。相比playbook, roles的結構更加的清晰有層次。

例如:我們無論安裝什么軟件都會安裝時間同步服務,那么每個playbook都要編寫時間同步服務的task。此時我們可以將時間同步服務task寫好,等到用的時候再調用就行了。

Ansible注意事項:在編寫roles的時候,最好能夠將一個task拆分為一個文件,方便后續復用。(徹底的打散)

2.Ansible Rose目錄結構

1.rose官方目錄的結構,必須那么定義

[root@m01 ~]# cd /etc/ansible/roles/
[root@m01 roles]# mkdir -p {nfs,rsync,web}/{vars,tasks,templates,handlers,files,meta}
[root@m01 roles]# tree
.
├── nfs
│   ├── files
│   ├── handlers
│   ├── meta
│   ├── tasks
│   ├── templates
│   └── vars
├── rsync
│   ├── files
│   ├── handlers
│   ├── meta
│   ├── tasks
│   ├── templates
│   └── vars
└── web
    ├── files
    ├── handlers
    ├── meta
    ├── tasks
    ├── templates
    └── vars

21 directories, 0 files

3.Ansible Rose依賴關系

roles允許您在使用role時自動引入其他role。role依賴關系存儲在role目錄中meta/main.yml文件中。

例如:安裝wordpress需要先確保nginx與php都能正常運行,此時可以在wordpress的role中定義,依賴nginx與php-fpm的roles

[root@m01 playbook]# cat /root/roles/wordpress/meta/main.yml
---
dependencies:
  - { role: nginx }
  - { role: php-fpm }

此時wordpress的role會先執行nginx的role,然后再執行php-fpm的role,最后再執行wordpress本身的role。

4.Ansible Rose案例實戰

Roles小技巧:
1.創建role 目錄結構,手動或使用ansible-galaxy init test roles
2.編寫role功能內容。
3.在playbook中引用

案例一、Ansible Roles重構NFS服務

1.安裝nfs的task任務
[root@m01 roles]# cat nfs/tasks/install.yml 
- name: Install NFS-utils Server
  yum: name=nfs-utils state=present

2.配置nfs的task任務
[root@m01 roles]# cat nfs/tasks/config.yml 
- name: Configure Nfs-utils Server
  template: src=./exports.j2 dest=/etc/exports owner=root group=root mode=0644
  notify: Restart NFS Server

3.啟動fs的task任務
[root@m01 roles]# cat nfs/tasks/start.yml 
- name: Start NFS Server
  systemd: name=nfs state=started enabled=yes

4.包含nfs的安裝、配置、啟動的task任務
[root@m01 roles]# cat nfs/tasks/main.yml 
- include_tasks: install.yml
- include_tasks: config.yml
- include_tasks: start.yml

5.客戶端掛載nfs的task任務
[root@m01 roles]# cat nfs-client/tasks/main.yml 
- name: Mount NFS Server
  mount: path=/opt src=172.16.1.7:/data fstype=nfs opts=defaults state=mounted

6.執行所有tasks的roles主配置文件
[root@m01 roles]# cat site.yml 
- hosts: web01
  roles:
    - nfs

- hosts: web02
  roles:
    - nfs-client

5.Ansible Galaxy

Galaxy是一個免費網站,類似於github網站,網站上基本都是共享的roles角色。從Galaxy下載roles角色是快速啟動自動化項目方式之一。galaxy官網

Ansible提供了一個ansible-galaxy 命令行工具,可以用來init(初始化)、search(查找)、install(安裝)、remove(移除)等操作。

1.如何使用ansible-galaxy 搜索一個項目

2.查看詳細信息

3.客戶端搜索項目,直接安裝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM